From 772462eeca3002a1d52508aeba485aab2b4742ad Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Tue, 03 Mar 2026 19:06:19 +0000
Subject: [PATCH] =MAJOR OVERHAUL. Likely should have made a new branch ages ago. Key changes: Registrar.php is the base for custom post types, taxonomies, and user roles. Replaces JVB_CONTENT, JVB_TAXONOMY, and JVB_USER constants, eliminates most of Features.php (except for JVB_SITE, JVB_MEMBERSHIP), and has built in sanitizing and validation via sub-classes. Also started a major overhaul of the Schema output. Created a shit ton of property traits and classes to help sanitize and ensure proper data for different schema types. Still a bunch to do, but better to be starting committing changes here on this other branch.

---
 inc/managers/SEO/render/Thing/CreativeWork/MediaObject/AudioObject.php                            |   14 
 inc/managers/SEO/render/Traits/_Properties/checkoutPageURLTemplateTrait.php                       |   22 
 inc/managers/SEO/schemas/resolvers/PersonResolver.php                                             |   33 
 inc/managers/SEO/render/Traits/_Properties/ownsTrait.php                                          |   48 
 inc/managers/SEO/render/Traits/_Properties/priceSpecificationTrait.php                            |   28 
 inc/managers/SEO/render/Traits/_Properties/producerTrait.php                                      |   32 
 inc/managers/SEO/render/Traits/_Properties/agentTrait.php                                         |   32 
 inc/managers/SEO/render/Thing/Intangible/MenuItem.php                                             |   20 
 inc/managers/SEO/render/Traits/_Properties/termsOfServiceTrait.php                                |   21 
 inc/admin/ContentTaxonomy.php                                                                     |   22 
 inc/managers/SEO/render/Traits/_Properties/maxValueTrait.php                                      |   21 
 inc/rest/routes/ApprovalRoutes.php                                                                |   13 
 inc/managers/SEO/render/Thing/Intangible/Enumeration/ItemListOrderType.php                        |   15 
 inc/managers/_setup.php                                                                           |    3 
 inc/managers/SEO/render/Traits/_Properties/photoTrait.php                                         |   34 
 inc/managers/SEO/render/Traits/_Properties/amenityFeatureTrait.php                                |   40 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/SelfStorage.php                          |   16 
 inc/managers/SEO/render/Traits/_Properties/eligibleCustomerTypeTrait.php                          |   28 
 src/fields/index.php                                                                              |    0 
 inc/managers/SEO/render/Thing/Intangible/Quantity/Mass.php                                        |   17 
 inc/managers/SEO/render/Traits/_Properties/acceptedPaymentMethodTrait.php                         |   31 
 inc/admin/SEOAdmin.php                                                                            |  389 
 inc/managers/SEO/render/Thing/CreativeWork/Clip.php                                               |   22 
 inc/managers/SEO/render/Traits/_Properties/unitTextTrait.php                                      |   21 
 inc/managers/SEO/render/Traits/_Properties/educationRequirementsTrait.php                         |   31 
 inc/managers/SEO/render/Thing/Intangible/ItemList/_setup.php                                      |    4 
 inc/managers/SEO/render/Traits/_Properties/durationTrait.php                                      |   23 
 inc/managers/SEO/render/Traits/_Properties/geoTrait.php                                           |   44 
 inc/managers/SEO/render/Traits/_Properties/sloganTrait.php                                        |   21 
 inc/managers/SEO/render/Thing/CreativeWork/EducationalOccupationalCredential.php                  |   38 
 inc/managers/SEO/render/Traits/_Properties/ratingValueTrait.php                                   |   21 
 inc/managers/SEO/render/Traits/_Properties/advancedBookingRequirementTrait.php                    |   23 
 inc/managers/SEO/render/Thing/Intangible/HowToTool.php                                            |   18 
 inc/managers/SEO/render/Traits/_Properties/ineligibleRegionTrait.php                              |   33 
 inc/managers/SEO/render/Traits/_Properties/certificationIdentificationTrait.php                   |   24 
 inc/managers/SEO/render/Traits/_Properties/paymentMethodTrait.php                                 |   75 
 inc/managers/SEO/render/Traits/_Properties/deathDateTrait.php                                     |   31 
 inc/managers/SEO/render/Traits/_Properties/creativeWorkStatusTrait.php                            |   23 
 inc/managers/SEO/render/Traits/_Properties/datePublishedTrait.php                                 |   27 
 inc/managers/SEO/render/Traits/_Properties/reviewedByTrait.php                                    |   32 
 inc/managers/SEO/render/Traits/_Properties/isFamilyFriendlyTrait.php                              |   21 
 inc/managers/SEO/render/Traits/_Properties/circleTrait.php                                        |   21 
 inc/managers/SEO/render/Thing/Intangible/Reservation/Reservation.php                              |   22 
 inc/managers/SEO/render/Thing/Intangible/OfferCatalog.php                                         |   13 
 inc/managers/SEO/render/Traits/_Properties/citationTrait.php                                      |   31 
 inc/managers/SEO/render/Traits/_Properties/hasCertificationTrait.php                              |   40 
 inc/managers/SEO/render/Traits/_Properties/associatedMediaTrait.php                               |   28 
 inc/managers/SEO/render/Traits/_Properties/publisherImprintTrait.php                              |   23 
 inc/managers/SEO/render/Traits/_Properties/experienceRequirementsTrait.php                        |   31 
 inc/managers/SEO/render/Traits/_Properties/suitableForDietTrait.php                               |   23 
 inc/managers/SEO/render/Thing/Intangible/Grant.php                                                |   15 
 inc/managers/SEO/render/Traits/_Properties/embeddedTextCaptionTrait.php                           |   21 
 inc/managers/SEO/render/Thing/Intangible/MerchantReturnPolicy.php                                 |   24 
 inc/managers/SEO/render/Traits/_Properties/itemReviewedTrait.php                                  |   23 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/AnimalShelter.php                        |   16 
 inc/forms/TaxonomySelector.php                                                                    |   17 
 inc/registrar/config/Integration.php                                                              |  122 
 inc/managers/SEO/render/Traits/_Properties/areaServedTrait.php                                    |   55 
 inc/managers/SEO/render/Traits/_Properties/longitudeTrait.php                                     |   30 
 inc/blocks/CustomBlocks.php                                                                       |    6 
 inc/managers/SEO/render/Traits/_Properties/nutritionTrait.php                                     |   23 
 inc/managers/SEO/render/Traits/_Properties/participantTrait.php                                   |   33 
 inc/blocks/RegisterBlocks.php                                                                     |   26 
 inc/registrar/config/seo/Schema.php                                                               |  297 
 inc/managers/SEO/render/Thing/Intangible/Reservation/FoodEstablishmentReservation.php             |   14 
 inc/managers/SEO/render/Traits/_Properties/maximumAttendeeCapacityTrait.php                       |   29 
 inc/managers/SEO/render/Traits/_Properties/extendedAddressTrait.php                               |   21 
 inc/managers/SEO/render/Traits/_Properties/startDateTrait.php                                     |   24 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/_setup.php             |   11 
 inc/managers/SEO/render/Traits/_Properties/licenseTrait.php                                       |   27 
 inc/managers/SEO/render/Traits/_Properties/legalRepresentativeTrait.php                           |   28 
 inc/managers/SEO/render/Traits/_Properties/previousStartDateTrait.php                             |   23 
 inc/managers/SEO/render/Thing/CreativeWork/MusicRecording.php                                     |   21 
 inc/managers/SEO/render/Traits/_Properties/founderTrait.php                                       |   29 
 inc/managers/SEO/render/Traits/_Properties/responsibilitiesTrait.php                              |   27 
 inc/managers/SEO/render/Traits/_Properties/monthsOfExperienceTrait.php                            |   21 
 inc/managers/SEO/SchemaBuilder.php                                                                |   19 
 inc/managers/SEO/render/Thing/Intangible/ItemList/ItemList.php                                    |   18 
 inc/managers/SEO/render/Thing/Intangible/Enumeration/DayOfWeek.php                                |   25 
 inc/managers/SEO/render/Traits/_Properties/mobileUrlTrait.php                                     |   27 
 inc/managers/SEO/render/Traits/_Properties/parentOrganizationTrait.php                            |   48 
 inc/managers/SEO/render/Traits/_Properties/interactionTypeTrait.php                               |   23 
 inc/managers/SEO/render/Traits/_Properties/servesCuisineTrait.php                                 |   21 
 inc/managers/SEO/render/Thing/Intangible/Enumeration/WearableSizeGroupEnumeration.php             |   33 
 inc/managers/SEO/render/Traits/_Properties/fatContentTrait.php                                    |   21 
 inc/managers/queue/Queue.php                                                                      |   80 
 inc/registrar/Fields.php                                                                          |  173 
 inc/managers/SEO/render/Traits/_Properties/endDateTrait.php                                       |   24 
 inc/managers/SEO/render/Traits/_Properties/highPriceTrait.php                                     |   26 
 inc/managers/SEO/render/Thing/Intangible/Enumeration/OrderStatusType.php                          |   25 
 inc/managers/SEO/render/Traits/_Properties/maximumVirtualAttendeeCapacityTrait.php                |   21 
 inc/managers/SEO/render/Traits/_Properties/dateCreatedTrait.php                                   |   31 
 inc/managers/SEO/render/Traits/_Properties/musicByTrait.php                                       |   24 
 inc/managers/SEO/render/Traits/_Properties/teachesTrait.php                                       |   25 
 inc/managers/SEO/render/Traits/_Properties/inDefinedTermSetTrait.php                              |   23 
 inc/managers/SEO/render/Thing/Intangible/Schedule.php                                             |   30 
 inc/managers/SEO/render/Traits/_Properties/companyRegistrationTrait.php                           |   26 
 inc/managers/SEO/render/Traits/_Properties/headlineTrait.php                                      |   21 
 inc/managers/SEO/render/Traits/_Properties/discussionUrlTrait.php                                 |   25 
 inc/managers/SEO/render/Traits/_Properties/codeValueTrait.php                                     |   21 
 inc/managers/SEO/render/Traits/_Properties/brokerTrait.php                                        |   32 
 inc/registrar/config/seo/Helpers.php                                                              |   29 
 inc/managers/SEO/render/SchemaOutput.php                                                          |  238 
 inc/managers/SEO/render/Traits/_Properties/previousItemTrait.php                                  |   23 
 inc/meta/Form.php                                                                                 |   91 
 inc/registrar/_setup.php                                                                          |   25 
 inc/managers/SEO/render/Traits/_Properties/menuAddOnTrait.php                                     |   32 
 inc/managers/SEO/schemas/resolvers/VisualArtworkResolver.php                                      |   20 
 inc/managers/SEO/render/Traits/_Properties/copyrightYearTrait.php                                 |   21 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/FastFoodRestaurant.php |   11 
 inc/managers/SEO/render/Traits/_Properties/maxPriceTrait.php                                      |   21 
 inc/managers/SEO/render/Traits/_Properties/availableChannelTrait.php                              |   23 
 inc/managers/SEO/render/Traits/_Properties/issuedByTrait.php                                      |   23 
 inc/managers/SEO/render/Thing/Product/_setup.php                                                  |    4 
 inc/managers/SEO/render/Traits/_Properties/contentReferenceTimeTrait.php                          |   28 
 inc/registrar/config/Dashboard.php                                                                |   44 
 src/fields/edit.js                                                                                |   29 
 inc/managers/SEO/render/Thing/Place/AdministrativeArea/Country.php                                |   12 
 inc/managers/SEO/render/Traits/_Properties/contentSizeTrait.php                                   |   21 
 base/taxonomies.php                                                                               |    2 
 inc/managers/SEO/render/Traits/_Properties/offerCountTrait.php                                    |   21 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/Restaurant.php         |   11 
 inc/managers/SEO/render/Thing/CreativeWork/WebPage/QAPage.php                                     |   15 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/Bakery.php             |   11 
 inc/managers/SEO/render/Thing/CreativeWork/MediaObject/ImageObject.php                            |   14 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/EmergencyService.php                     |   16 
 inc/managers/SEO/render/Traits/_Properties/containedInPlaceTrait.php                              |   24 
 inc/managers/SEO/render/Thing/Intangible/ListItem.php                                             |   18 
 inc/managers/SEO/render/Traits/_Properties/targetTrait.php                                        |   23 
 inc/registrar/helpers/MakeCalendarType.php                                                        |  334 
 inc/utility/Checker.php                                                                           |   47 
 inc/managers/SEO/render/Thing/CreativeWork/MenuSection.php                                        |   13 
 inc/rest/_setup.php                                                                               |    6 
 inc/managers/SEO/render/Thing/Intangible/_setup.php                                               |   33 
 inc/managers/SEO/render/Traits/Place/PlaceSchema.php                                              |   45 
 inc/managers/SEO/render/Traits/_Properties/publicAccessTrait.php                                  |   30 
 inc/managers/SEO/render/Thing/CreativeWork/WebPage/ItemPage.php                                   |   15 
 inc/managers/SEO/render/Thing/CreativeWork/MediaObject/MediaObject.php                            |   58 
 inc/managers/SEO/render/Traits/_Properties/encodingTypeTrait.php                                  |   27 
 inc/managers/SEO/render/Traits/_Properties/providerTrait.php                                      |   32 
 inc/managers/SEO/render/Traits/_Properties/numberOfItemsTrait.php                                 |   21 
 inc/managers/SEO/render/Traits/_Properties/subEventTrait.php                                      |   29 
 inc/managers/SEO/render/Traits/_Properties/attendeeTrait.php                                      |   32 
 inc/managers/SEO/render/Traits/_Properties/byMonthDayTrait.php                                    |   45 
 inc/managers/SEO/render/Traits/_Properties/sponsorTrait.php                                       |   32 
 inc/managers/SEO/render/Thing/Intangible/Enumeration/PhysicalActivityCategory.php                 |   19 
 inc/managers/SEO/render/Traits/_Properties/ownerTrait.php                                         |   56 
 inc/managers/SEO/render/Traits/_Properties/repeatCountTrait.php                                   |   21 
 inc/managers/SEO/render/Traits/_Properties/inLanguageTrait.php                                    |   31 
 inc/managers/SEO/render/Traits/_Properties/weightTrait.php                                        |   24 
 inc/managers/SEO/render/Traits/_Properties/publisherTrait.php                                     |   32 
 inc/rest/routes/LoginRoutes.php                                                                   |    5 
 inc/managers/SEO/render/Traits/_Properties/ineligibleLocationTrait.php                            |   33 
 inc/managers/SEO/render/Traits/_Properties/genreTrait.php                                         |   31 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/ChildCare.php                            |   16 
 inc/managers/SEO/render/Traits/_Properties/reviewTrait.php                                        |   67 
 inc/managers/SEO/render/Traits/_Properties/dateModifiedTrait.php                                  |   27 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/TelevisionStation.php                    |   16 
 inc/managers/SEO/render/Traits/_Properties/itemListElementTrait.php                               |   33 
 inc/managers/SEO/render/Thing/Intangible/Enumeration/SizeSpecification.php                        |   35 
 inc/managers/SEO/render/Traits/_Properties/descriptionTrait.php                                   |   21 
 inc/managers/SEO/render/Traits/_Properties/givenNameTrait.php                                     |   21 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/ProfessionalService.php                  |   16 
 inc/managers/SEO/render/Traits/_Properties/hasMerchantReturnPolicyTrait.php                       |   23 
 inc/managers/SEO/render/Traits/_Properties/hasVariantTrait.php                                    |   29 
 inc/managers/SEO/render/Traits/_Properties/sdPublisherTrait.php                                   |   24 
 inc/managers/CRUDManager.php                                                                      |  120 
 inc/managers/SEO/render/Traits/_Properties/deathPlaceTrait.php                                    |   23 
 inc/managers/SEO/render/Traits/_Properties/hasPOSTrait.php                                        |   23 
 inc/managers/SEO/render/Traits/_Properties/subOrganizationTrait.php                               |   48 
 inc/managers/SEO/render/Traits/_Properties/thumbnailUrlTrait.php                                  |   21 
 inc/managers/SEO/render/Thing/Intangible/Enumeration/SizeGroupEnumeration.php                     |   15 
 inc/managers/SEO/render/Thing/Intangible/ContactPoint/_setup.php                                  |    2 
 inc/managers/SEO/render/Traits/_Properties/departmentTrait.php                                    |   26 
 inc/managers/SEO/render/Traits/_Properties/_setup.php                                             |  447 +
 inc/managers/SEO/render/Traits/_Properties/isVariantOfTrait.php                                   |   27 
 inc/managers/SEO/render/Traits/_Properties/urlTemplateTrait.php                                   |   22 
 inc/managers/SEO/render/Traits/_Properties/organizerTrait.php                                     |   32 
 inc/managers/SEO/render/Thing/Intangible/Enumeration/QualitativeValue.php                         |   48 
 inc/managers/DirectoryManager.php                                                                 |  127 
 inc/managers/SEO/render/Traits/_Properties/translationOfWorkTrait.php                             |   24 
 inc/blocks/FeedBlock.php                                                                          |   78 
 inc/managers/SEO/render/Thing/CreativeWork/MediaObject/_setup.php                                 |    5 
 inc/managers/SEO/render/Thing/Intangible/Occupation.php                                           |   22 
 inc/managers/SEO/render/Thing/CreativeWork/Certification.php                                      |   26 
 inc/managers/SEO/render/Traits/_Properties/authorTrait.php                                        |   26 
 inc/managers/SEO/render/Traits/_Properties/processingTimeTrait.php                                |   23 
 inc/managers/SEO/render/Traits/_Properties/amountOfThisGoodTrait.php                              |   21 
 inc/managers/SEO/render/Thing/Intangible/StructuredValue/ServicePeriod.php                        |   18 
 inc/managers/SEO/render/Traits/_Properties/recordedAtTrait.php                                    |   24 
 inc/managers/SEO/render/Traits/_Properties/byDayTrait.php                                         |   31 
 inc/managers/SEO/render/Traits/_Properties/hasCategoryCodeTrait.php                               |   28 
 inc/managers/SEO/render/Traits/_Properties/addOnTrait.php                                         |   28 
 inc/managers/SEO/render/Traits/_Properties/potentialActionTrait.php                               |  149 
 inc/managers/SEO/render/Traits/_Properties/knowsAboutTrait.php                                    |   28 
 inc/managers/SEO/render/Traits/_Properties/videoTrait.php                                         |   24 
 inc/managers/AdminPages.php                                                                       |   34 
 assets/css/nav.min.css                                                                            |    2 
 inc/managers/SEO/render/Traits/_Properties/businessDaysTrait.php                                  |   32 
 inc/registrar/helpers/MakeTrackChanges.php                                                        |   10 
 inc/managers/SEO/render/Thing/Intangible/Brand/_setup.php                                         |    2 
 inc/managers/SEO/render/Traits/_Properties/offersTrait.php                                        |   63 
 inc/managers/SEO/render/Traits/_Properties/smokingAllowedTrait.php                                |   31 
 inc/managers/SEO/render/Thing/Intangible/StructuredValue/PropertyValue.php                        |   16 
 inc/managers/SEO/render/Traits/_Properties/serviceLocationTrait.php                               |   28 
 inc/rest/RestRouteManager.php                                                                     |   27 
 inc/managers/SEO/render/Traits/_Properties/fundingTrait.php                                       |   29 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/RadioStation.php                         |   16 
 inc/managers/SEO/render/Traits/_Properties/exceptDateTrait.php                                    |   32 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/FoodEstablishment.php  |   16 
 inc/managers/SEO/render/Traits/_Properties/propertyIDTrait.php                                    |   22 
 inc/managers/CustomTable.php                                                                      |  226 
 inc/managers/SEO/render/Traits/_Properties/transcriptTrait.php                                    |   21 
 inc/managers/SEO/render/Traits/_Properties/boxTrait.php                                           |   21 
 inc/managers/SEO/render/Traits/_Properties/deliveryLeadTimeTrait.php                              |   23 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/InternetCafe.php                         |   16 
 inc/managers/SEO/render/Traits/_Properties/doorTimeTrait.php                                      |   24 
 jvb.php                                                                                           |   37 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/RecyclingCenter.php                      |   16 
 inc/managers/SEO/render/Thing/Intangible/StructuredValue/PriceSpecification.php                   |   22 
 inc/managers/SEO/render/Traits/_Properties/actionProcessTrait.php                                 |   23 
 inc/managers/SEO/render/Thing/Intangible/Rating/Rating.php                                        |   18 
 src/feed/render.php                                                                               |  247 
 inc/managers/DashboardManager.php                                                                 |  139 
 inc/managers/SEO/render/Traits/_Properties/currencyTrait.php                                      |   23 
 inc/managers/SEO/render/Traits/_Properties/interactionStatisticTrait.php                          |   23 
 inc/managers/SEO/render/Traits/_Properties/captionTrait.php                                       |   21 
 inc/managers/SEO/render/Thing/Intangible/Enumeration/WarrantyScope.php                            |   20 
 inc/managers/SEO/render/Traits/_Properties/workFeaturedTrait.php                                  |   28 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/_setup.php                               |   28 
 inc/managers/SEO/render/Thing/Person/_setup.php                                                   |    2 
 inc/managers/SEO/render/Traits/_Properties/aboutTrait.php                                         |   24 
 inc/rest/routes/ShopRoutes.php                                                                    |    3 
 inc/managers/SEO/render/Traits/_Properties/foundingLocationTrait.php                              |   42 
 inc/managers/SEO/render/Traits/_Properties/isSimilarToTrait.php                                   |   32 
 inc/rest/routes/NewsRoutes.php                                                                    |    2 
 inc/managers/SEO/render/Traits/_Properties/auditDateTrait.php                                     |   24 
 inc/managers/SEO/render/Traits/_Properties/widthTrait.php                                         |   24 
 inc/managers/SEO/render/Thing/Intangible/Language.php                                             |   12 
 inc/managers/SEO/render/Thing/Organization/Organization.php                                       |   13 
 inc/managers/SEO/render/Thing/Properties/_setup.php                                               |    3 
 inc/managers/SEO/render/Thing/Intangible/Quantity/Duration.php                                    |   17 
 inc/managers/SEO/render/Traits/_Properties/geographicAreaTrait.php                                |   23 
 inc/registrar/helpers/MakeVerification.php                                                        |   10 
 inc/rest/routes/UploadRoutes.php                                                                  |   10 
 inc/managers/SEO/render/Traits/_Properties/siblingTrait.php                                       |   31 
 inc/managers/SEO/SchemaOutputManager.php                                                          |   30 
 inc/managers/SEO/render/Traits/_Properties/imageTrait.php                                         |   33 
 inc/managers/SEO/render/Traits/_Properties/knowsTrait.php                                         |   31 
 inc/managers/SEO/render/Traits/_Properties/foundingDateTrait.php                                  |   37 
 inc/managers/SEO/render/Thing/CreativeWork/CreativeWork.php                                       |  115 
 inc/managers/SEO/render/Traits/_Properties/inCodeSetTrait.php                                     |   23 
 inc/managers/SEO/render/Thing/Place/AdministrativeArea/_setup.php                                 |    3 
 inc/managers/SEO/render/Traits/_Properties/bookingTimeTrait.php                                   |   21 
 inc/managers/SEO/render/Traits/_Properties/videoQualityTrait.php                                  |   21 
 inc/managers/SEO/render/Traits/_Properties/naicsTrait.php                                         |   21 
 inc/managers/SEO/render/Thing/Intangible/StructuredValue/MonetaryAmount.php                       |   17 
 inc/managers/SEO/render/Traits/_Properties/typeOfGoodTrait.php                                    |   32 
 inc/helpers/terms.php                                                                             |   63 
 inc/managers/SEO/render/Traits/_Properties/isBasedOnTrait.php                                     |   33 
 inc/managers/SEO/render/Traits/_Properties/reservationIdTrait.php                                 |   21 
 inc/managers/SEO/render/Traits/_Properties/_a.php                                                 |   21 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/LegalService.php                         |   16 
 inc/managers/SEO/render/Traits/_Properties/eligibleDurationTrait.php                              |   23 
 inc/managers/SEO/render/Traits/_Properties/followsTrait.php                                       |   31 
 inc/managers/SEO/render/Traits/_Properties/memberTrait.php                                        |   33 
 inc/managers/SEO/render/Traits/_Properties/gtinTrait.php                                          |   27 
 inc/managers/SEO/render/Traits/_Properties/mentionsTrait.php                                      |   28 
 inc/managers/SEO/render/Traits/_Properties/providesServiceTrait.php                               |   28 
 inc/managers/SEO/render/Traits/_Properties/variesByTrait.php                                      |   31 
 inc/managers/SEO/TemplateResolver.php                                                             |   59 
 inc/managers/SEO/render/Traits/_Properties/hasAdultConsiderationTrait.php                         |   23 
 inc/managers/SEO/render/Traits/_Properties/encodingTrait.php                                      |   24 
 inc/managers/SEO/render/Traits/_Properties/streetAddressTrait.php                                 |   21 
 inc/managers/SEO/render/Traits/_Properties/hasDriveThroughServiceTrait.php                        |   29 
 inc/managers/SEO/render/Thing/Intangible/PaymentMethod.php                                        |   34 
 inc/managers/SEO/render/Thing/CreativeWork/WebPage/SearchResultsPage.php                          |   15 
 inc/managers/SEO/render/Traits/_Properties/performerInTrait.php                                   |   28 
 inc/managers/SEO/render/Traits/_Properties/byMonthWeekTrait.php                                   |   49 
 inc/managers/SEO/render/Traits/_Properties/isRelatedToTrait.php                                   |   32 
 inc/managers/SEO/render/Thing/Product/ProductGroup.php                                            |   15 
 inc/managers/SEO/render/Traits/_Properties/sugarContentTrait.php                                  |   21 
 inc/managers/SEO/render/Thing/Place/AdministrativeArea/AdministrativeArea.php                     |   17 
 inc/managers/SEO/render/Traits/_Properties/textTrait.php                                          |   21 
 inc/managers/SEO/render/Thing/Action.php                                                          |   37 
 inc/managers/SEO/render/Traits/_Properties/cholesterolContentTrait.php                            |   21 
 inc/managers/SEO/render/Thing/Intangible/Enumeration/RestrictedDiet.php                           |   24 
 inc/managers/SEO/render/Traits/_Properties/knowsLanguageTrait.php                                 |   28 
 inc/managers/SEO/render/Thing/Intangible/StructuredValue/GeoCoordinates.php                       |   18 
 inc/managers/SEO/render/Traits/_Properties/openingHoursSpecificationTrait.php                     |   54 
 inc/managers/SEO/render/Thing/Intangible/DefinedTerm.php                                          |   17 
 inc/registrar/Users.php                                                                           |    0 
 inc/managers/SEO/render/Traits/_Properties/pronounsTrait.php                                      |   26 
 inc/managers/SEO/render/Traits/_Properties/translatorTrait.php                                    |   32 
 inc/managers/SEO/render/Thing/Product/ProductModel.php                                            |   14 
 inc/managers/SEO/render/Traits/ThingSchema.php                                                    |  121 
 inc/managers/SEO/render/Traits/_Properties/workPerformedTrait.php                                 |   28 
 inc/managers/SEO/render/Traits/_Properties/acquireLicensePageTrait.php                            |   29 
 inc/managers/SEO/render/Thing/Intangible/OccupationalExperienceRequirements.php                   |   15 
 inc/managers/SEO/render/Traits/_Helpers/_setup.php                                                |    2 
 inc/managers/SEO/render/Traits/_Properties/sdDatePublishedTrait.php                               |   23 
 inc/managers/SEO/render/Traits/_Properties/dayOfWeekTrait.php                                     |   23 
 inc/managers/SEO/render/Traits/_Properties/exifDataTrait.php                                      |   23 
 inc/managers/SEO/render/Traits/_Properties/skillsTrait.php                                        |   28 
 inc/managers/SEO/render/Traits/_Properties/parentTrait.php                                        |   31 
 inc/managers/SEO/render/Traits/_Properties/priceRangeTrait.php                                    |   30 
 inc/managers/SEO/render/Traits/_Properties/serviceOutputTrait.php                                 |   28 
 inc/managers/SEO/render/Thing/Intangible/StructuredValue/StructuredValue.php                      |   13 
 inc/managers/SEO/render/Traits/_Properties/includesObjectTrait.php                                |   28 
 inc/helpers/crud.php                                                                              |    4 
 inc/managers/SEO/render/Traits/_Properties/aggregateRatingTrait.php                               |   55 
 JVBase.php                                                                                        |   47 
 inc/managers/SEO/render/Traits/_Properties/aggregateElementTrait.php                              |   23 
 inc/meta/MetaManager.php                                                                          |   42 
 inc/managers/SEO/render/Traits/_Properties/creditTextTrait.php                                    |   21 
 inc/managers/SEO/render/Traits/_Properties/copyrightNoticeTrait.php                               |   21 
 inc/managers/SEO/render/Traits/_Properties/countryOfAssemblyTrait.php                             |   21 
 inc/managers/SEO/render/Thing/CreativeWork/Review.php                                             |   19 
 inc/managers/SEO/render/Traits/_Properties/addressLocalityTrait.php                               |   21 
 inc/managers/SEO/render/Traits/_Properties/subjectOfTrait.php                                     |   33 
 inc/registrar/config/TrackChanges.php                                                             |    0 
 inc/registrar/config/seo/Archive.php                                                              |   10 
 inc/managers/SEO/render/Traits/_Properties/eventTrait.php                                         |   28 
 inc/managers/SEO/render/seo/_setup.php                                                            |    5 
 inc/managers/SEO/render/Thing/Intangible/Audience.php                                             |   16 
 inc/managers/SEO/render/Thing/Event/_setup.php                                                    |    2 
 inc/meta/Meta.php                                                                                 |    4 
 inc/managers/SEO/render/Thing/Intangible/Brand/Brand.php                                          |   18 
 inc/managers/SEO/render/Traits/_Properties/audioTrait.php                                         |   34 
 inc/managers/SEO/render/Traits/_Properties/carbohydrateContentTrait.php                           |   21 
 inc/managers/SEO/render/Traits/_Properties/postalAddressTrait.php                                 |   23 
 inc/managers/SEO/render/Traits/_Properties/postalCodeTrait.php                                    |   21 
 inc/managers/SEO/render/Traits/_Properties/hasOccupationTrait.php                                 |   28 
 inc/managers/SEO/render/Traits/_Properties/sellerTrait.php                                        |   32 
 src/fields/block.json                                                                             |   25 
 checks.php                                                                                        |  208 
 inc/managers/SEO/render/Traits/_Properties/editorTrait.php                                        |   23 
 inc/registrar/config/Verification.php                                                             |    0 
 inc/managers/SEO/render/Thing/Intangible/VirtualLocation.php                                      |   13 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/RealEstateAgent.php                      |   16 
 inc/managers/SEO/render/Thing/Intangible/Enumeration/EventStatusType.php                          |   22 
 inc/managers/SEO/render/Traits/_Properties/bestRatingTrait.php                                    |   21 
 inc/managers/SEO/render/Traits/_Helpers/arrayHelper.php                                           |   93 
 inc/managers/SEO/render/Thing/Intangible/AggregateOffer.php                                       |   33 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/FinancialService.php                     |   16 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/Store.php                                |   16 
 inc/managers/SEO/render/Traits/_Properties/minValueTrait.php                                      |   21 
 inc/managers/SEO/render/Traits/_Properties/caloriesTrait.php                                      |   21 
 inc/managers/SEO/SchemaReferenceBuilder.php                                                       |   82 
 inc/helpers/ui.php                                                                                |    2 
 inc/managers/SEO/render/Thing/Place/Place.php                                                     |   12 
 inc/managers/SEO/render/Traits/_Properties/alternateNameTrait.php                                 |   30 
 inc/registrar/Terms.php                                                                           |  255 
 inc/managers/SEO/render/Traits/_Properties/nameTrait.php                                          |   21 
 inc/registry/TaxonomyRegistrar.php                                                                |   21 
 inc/managers/SEO/render/Traits/_Properties/instrumentTrait.php                                    |   23 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/ShoppingCenter.php                       |   16 
 inc/rest/routes/OptionsRoutes.php                                                                 |    3 
 inc/managers/SEO/render/Traits/_Properties/urlTrait.php                                           |   34 
 inc/managers/SEO/render/Traits/_Properties/exampleOfWorkTrait.php                                 |   29 
 inc/managers/SEO/render/Traits/_Properties/spatialCoverageTrait.php                               |   23 
 inc/managers/SEO/render/Traits/_Properties/numberOfEmployeesTrait.php                             |   23 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/AutomotiveBusiness.php                   |   16 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/MedicalBusiness/MedicalBusiness.php      |   11 
 inc/managers/SEO/render/Traits/_Properties/warrantyTrait.php                                      |   23 
 inc/managers/SEO/render/Traits/_Properties/partySizeTrait.php                                     |   21 
 inc/managers/SEO/render/Traits/_Properties/contactTypeTrait.php                                   |   21 
 inc/managers/SEO/render/Traits/_Properties/endTimeTrait.php                                       |   26 
 inc/managers/SEO/render/Thing/Intangible/Enumeration/DeliveryMethod.php                           |   26 
 inc/managers/SEO/render/Traits/_Properties/occupationLocationTrait.php                            |   28 
 inc/managers/SEO/render/Thing/Intangible/Enumeration/BusinessEntityType.php                       |   20 
 inc/managers/SEO/render/Traits/_Properties/occupationalCategoryTrait.php                          |   36 
 inc/managers/SEO/render/Traits/_Properties/minPriceTrait.php                                      |   21 
 inc/managers/SEO/render/Traits/_Properties/contentUrlTrait.php                                    |   25 
 inc/managers/SEO/render/Thing/CreativeWork/WebPage/WebPage.php                                    |   22 
 inc/managers/SEO/render/Traits/_Properties/issnTrait.php                                          |   21 
 inc/managers/SEO/render/Traits/_Properties/certificationStatusTrait.php                           |   23 
 src/fields/index.js                                                                               |    0 
 inc/managers/SEO/render/Traits/_Properties/seeksTrait.php                                         |   39 
 inc/managers/SEO/render/Traits/_Properties/availabilityTrait.php                                  |   23 
 inc/managers/SEO/render/Traits/_Properties/repeatFrequencyTrait.php                               |   23 
 inc/helpers/dashboard.php                                                                         |  112 
 inc/meta/MetaTypeManager.php                                                                      |    9 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/EntertainmentBusiness.php                |   16 
 inc/managers/SEO/render/Traits/_Properties/opensTrait.php                                         |   23 
 inc/managers/SEO/render/Traits/_Properties/servingSizeTrait.php                                   |   21 
 inc/meta/Sanitizer.php                                                                            |   18 
 inc/registrar/config/Section.php                                                                  |  111 
 inc/managers/SEO/render/Thing/Intangible/StructuredValue/LocationFeatureSpecification.php         |   14 
 inc/managers/SEO/render/Traits/_Properties/availabilityEndsTrait.php                              |   25 
 inc/managers/SEO/render/Traits/_Properties/hasPartTrait.php                                       |   29 
 base/seo.php                                                                                      |    2 
 inc/managers/SEO/render/Traits/_Properties/durationOfWarrantyTrait.php                            |   24 
 inc/managers/SEO/render/Thing/Intangible/Quantity/Distance.php                                    |   17 
 inc/managers/SEO/render/Thing/Intangible/StructuredValue/QuantitativeValue.php                    |   21 
 inc/managers/SEO/render/Thing/Intangible/Rating/_setup.php                                        |    4 
 inc/managers/SEO/render/Traits/_Properties/relatedToTrait.php                                     |   31 
 inc/managers/SEO/render/Thing/Place/_setup.php                                                    |    3 
 inc/managers/SEO/render/Traits/_Properties/isConsumableForTrait.php                               |   28 
 inc/managers/SEO/render/Traits/_setup.php                                                         |    7 
 inc/managers/SEO/render/Thing/Intangible/Enumeration/_setup.php                                   |   27 
 inc/managers/SEO/render/Traits/_Properties/alternativeHeadlineTrait.php                           |   21 
 inc/managers/SEO/render/Thing/Event/Event.php                                                     |   54 
 inc/managers/SEO/render/Traits/_Properties/inventoryLevelTrait.php                                |   23 
 inc/managers/SEO/render/Traits/_Properties/totalPriceTrait.php                                    |   21 
 inc/managers/SEO/render/Traits/_Properties/actionStatusTrait.php                                  |   23 
 inc/rest/routes/ContentRoutes.php                                                                 |   62 
 inc/managers/SEO/render/Traits/_Properties/telephoneTrait.php                                     |   35 
 inc/managers/SEO/render/Traits/_Properties/embedURLTrait.php                                      |   25 
 assets/js/concise/CRUD.js                                                                         |   17 
 inc/managers/SEO/render/Traits/_Properties/hasMapTrait.php                                        |   33 
 inc/managers/SEO/render/Traits/Organization/_setup.php                                            |    2 
 inc/managers/SEO/render/Traits/_Properties/colleagueTrait.php                                     |   31 
 inc/managers/SEO/render/Traits/_Properties/additionalNameTrait.php                                |   27 
 inc/managers/SEO/render/Traits/_Properties/scheduleTimezoneTrait.php                              |   21 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/BarOrPub.php           |   11 
 inc/managers/SEO/render/Traits/_Properties/sdLicenseTrait.php                                     |   27 
 inc/managers/SEO/render/Thing/Intangible/HowToSupply.php                                          |   15 
 inc/registrar/config/Management.php                                                               |    0 
 inc/managers/SEO/render/Traits/_Properties/additionalPropertyTrait.php                            |   54 
 inc/managers/SEO/render/Traits/_Properties/audienceTypeTrait.php                                  |   21 
 inc/managers/SEO/render/Traits/_Properties/validFromTrait.php                                     |   24 
 inc/managers/SEO/render/Thing/CreativeWork/HowToStep.php                                          |   16 
 inc/managers/SEO/render/Traits/_Properties/workTranslationTrait.php                               |   24 
 inc/managers/SEO/render/Traits/_Properties/sameAsTrait.php                                        |   48 
 inc/managers/SEO/render/Traits/_Properties/termCodeTrait.php                                      |   21 
 inc/managers/SEO/render/Traits/_Properties/patternTrait.php                                       |   31 
 inc/managers/SEO/render/Traits/_Properties/worstRatingTrait.php                                   |   22 
 inc/managers/SEO/render/Traits/_Properties/isAccessibleForFreeTrait.php                           |   29 
 inc/managers/SEO/ConfigManager.php                                                                |   24 
 inc/managers/SEO/render/Traits/_Properties/manufacturerTrait.php                                  |   23 
 inc/registrar/Labels.php                                                                          |    1 
 inc/managers/SEO/render/Traits/_Properties/bitrateTrait.php                                       |   21 
 inc/managers/SEO/render/Thing/CreativeWork/_setup.php                                             |   19 
 inc/managers/SEO/render/Traits/_Properties/legalNameTrait.php                                     |   21 
 inc/rest/routes/ContentTermsRoutes.php                                                            |   26 
 inc/managers/SEO/render/Traits/_Properties/contentLocationTrait.php                               |   28 
 inc/managers/SEO/render/Traits/_Properties/addressCountryTrait.php                                |   23 
 inc/registrar/fields/GroupedField.php                                                             |   24 
 inc/managers/SEO/render/Traits/_Properties/employeeTrait.php                                      |   33 
 inc/managers/SEO/SEOAdminPage.php                                                                 |   28 
 inc/managers/SEO/render/Traits/_Properties/spouseTrait.php                                        |   31 
 inc/managers/SEO/render/Thing/Intangible/Enumeration/SizeSystemEnumeration.php                    |   20 
 inc/managers/SEO/render/Traits/_Properties/typicalAgeRangeTrait.php                               |   21 
 inc/admin/_setup.php                                                                              |    6 
 inc/managers/SEO/render/Traits/_Properties/contactPointTrait.php                                  |   28 
 inc/managers/SEO/render/Traits/_Properties/expiresTrait.php                                       |   24 
 inc/managers/SEO/render/Traits/_Properties/brandTrait.php                                         |   32 
 inc/managers/SEO/render/Traits/_Properties/eligibleQuantityTrait.php                              |   23 
 inc/managers/SEO/render/Traits/_Properties/specialOpeningHoursTrait.php                           |   61 
 inc/managers/SEO/render/Traits/_Properties/branchCodeTrait.php                                    |   23 
 inc/managers/SEO/render/Traits/_Properties/mainEntityOfPageTrait.php                              |   24 
 inc/registrar/config/Breadcrumbs.php                                                              |   48 
 inc/integrations/Helcim.php                                                                       |   11 
 inc/managers/SEO/render/Traits/_Properties/countryTrait.php                                       |   24 
 inc/managers/SEO/render/Traits/_Properties/byArtistTrait.php                                      |   29 
 inc/managers/SEO/render/Thing/Organization/PerformingGroup.php                                    |    9 
 inc/managers/SEO/render/Traits/_Properties/prepTimeTrait.php                                      |   23 
 inc/managers/SEO/render/Traits/_Properties/superEventTrait.php                                    |   24 
 inc/managers/SEO/render/Thing/Intangible/Enumeration/StatusEnumeration.php                        |   15 
 inc/managers/SEO/render/Traits/_Properties/representativeOfPageTrait.php                          |   21 
 inc/managers/SEO/render/Traits/_Properties/byMonthTrait.php                                       |   49 
 inc/managers/SEO/render/Traits/_Properties/temporalCoverageTrait.php                              |   25 
 inc/managers/SEO/render/Thing/CreativeWork/WebPage/ContactPage.php                                |   15 
 inc/managers/SEO/render/Traits/_Properties/skuTrait.php                                           |   23 
 inc/managers/SEO/render/Thing/CreativeWork/MediaObject/VideoObject.php                            |   20 
 inc/managers/SEO/_setup.php                                                                       |   26 
 inc/managers/SEO/render/Traits/_Properties/hasMenuSectionTrait.php                                |   28 
 inc/managers/SEO/render/Traits/_Properties/totalTimeTrait.php                                     |   23 
 inc/managers/queue/executors/ContentExecutor.php                                                  |   11 
 inc/managers/SEO/render/Traits/_Properties/spatialTrait.php                                       |   23 
 inc/managers/SEO/render/Thing/Intangible/Enumeration/GovernmentBenefitsType.php                   |   20 
 inc/managers/SEO/render/Traits/_Properties/breadcrumbTrait.php                                    |   23 
 inc/helpers/all.php                                                                               |    9 
 inc/managers/SEO/render/Traits/_Properties/videoFrameSizeTrait.php                                |   21 
 inc/managers/SEO/render/Traits/_Properties/sourceOrganizationTrait.php                            |   23 
 inc/managers/SEO/render/Traits/_Properties/audienceTrait.php                                      |   23 
 inc/managers/SEO/render/Traits/_Properties/hasMenuItemTrait.php                                   |   28 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/Dentist.php                              |   16 
 inc/managers/SEO/render/Traits/_Properties/performTimeTrait.php                                   |   23 
 inc/managers/SEO/render/Traits/_Properties/negativeNotesTrait.php                                 |   33 
 inc/managers/SEO/render/Traits/_Properties/serialNumberTrait.php                                  |   21 
 inc/managers/SEO/render/Thing/CreativeWork/WebPage/MedicalWebPage.php                             |   15 
 inc/managers/SEO/render/Thing/Intangible/Enumeration/ActionStatusType.php                         |   21 
 inc/managers/SEO/render/Thing/Intangible/Offer.php                                                |   66 
 inc/managers/SEO/render/Traits/_Properties/colorTrait.php                                         |   27 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/Distillery.php         |   11 
 inc/managers/SEO/render/Traits/_Properties/regionsAllowedTrait.php                                |   33 
 inc/managers/SEO/render/Thing/Organization/_setup.php                                             |    6 
 inc/registrar/fields/Calendar.php                                                                 |    0 
 inc/managers/SEO/render/Thing/Intangible/Enumeration/RefundTypeEnumeration.php                    |   15 
 inc/managers/SEO/render/Traits/_Properties/releaseDateTrait.php                                   |   23 
 inc/managers/SEO/render/Thing/Intangible/StructuredValue/NutritionalInformation.php               |   26 
 inc/managers/SEO/render/Traits/_Properties/warrantyScopeTrait.php                                 |   23 
 inc/managers/SEO/render/Traits/_Properties/interactionServiceTrait.php                            |   23 
 inc/managers/SEO/render/Traits/_Properties/ethicsPolicyTrait.php                                  |   29 
 inc/managers/SEO/render/Traits/Place/_setup.php                                                   |    2 
 inc/managers/SEO/render/Traits/Organization/OrganizationSchema.php                                |   79 
 src/summary/render.php                                                                            |   10 
 inc/managers/SEO/render/Traits/_Properties/unitCodeTrait.php                                      |   21 
 inc/managers/SEO/render/Traits/_Properties/itemListOrderTrait.php                                 |   23 
 inc/managers/SEO/render/Traits/_Properties/polygonTrait.php                                       |   21 
 inc/registrar/config/seo/Resolver.php                                                             |  179 
 inc/managers/SEO/render/Thing/CreativeWork/HowTo.php                                              |   42 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/LodgingBusiness.php                      |   16 
 inc/managers/SEO/render/Thing/CreativeWork/DefinedTermSet.php                                     |   15 
 inc/managers/SEO/render/Traits/_Properties/primaryImageOfPageTrait.php                            |   23 
 inc/managers/SEO/render/DataType/Time.php                                                         |   33 
 inc/integrations/Square.php                                                                       |   57 
 inc/managers/SEO/render/Traits/_Properties/closesTrait.php                                        |   23 
 inc/managers/SEO/render/Traits/_Properties/saturatedFatContentTrait.php                           |   21 
 inc/integrations/Integrations.php                                                                 |   96 
 inc/rest/routes/SEORoutes.php                                                                     |    7 
 inc/managers/SEO/render/Thing/Intangible/ServiceChannel.php                                       |   25 
 inc/managers/SEO/render/Traits/_Properties/hoursAvailableTrait.php                                |   25 
 inc/managers/SEO/render/Traits/_Properties/locationTrait.php                                      |   81 
 inc/managers/SEO/render/Traits/_Properties/availableAtOrFromTrait.php                             |   28 
 inc/managers/SEO/render/_setup.php                                                                |    5 
 inc/managers/SEO/render/Traits/_Properties/inGroupProductWithIDTrait.php                          |   21 
 inc/managers/SEO/render/Traits/_Properties/globalLocationNumberTrait.php                          |   21 
 inc/managers/SEO/render/Traits/_Properties/lineTrait.php                                          |   21 
 inc/managers/SEO/render/Traits/_Properties/locationCreatedTrait.php                               |   28 
 inc/registrar/config/Directory.php                                                                |   73 
 activate.php                                                                                      |   59 
 inc/managers/SEO/render/Thing/CreativeWork/WebPage/CollectionPage.php                             |   21 
 inc/managers/SEO/render/Thing/Intangible/Enumeration/OfferItemCondition.php                       |   16 
 inc/managers/SEO/render/Traits/_Properties/refundTypeTrait.php                                    |   23 
 inc/managers/SEO/render/Traits/_Properties/isAccessoryOrSparePartForTrait.php                     |   28 
 inc/managers/SEO/render/Thing/Intangible/CategoryCode.php                                         |   14 
 inc/managers/SEO/render/Traits/_Properties/worksAtTrait.php                                       |   32 
 inc/managers/SEO/render/Thing/Intangible/Demand.php                                               |   36 
 inc/managers/SEO/render/Traits/_Properties/positionTrait.php                                      |   21 
 inc/managers/SEO/render/Thing/CreativeWork/Menu.php                                               |   13 
 inc/registrar/config/Responses.php                                                                |    0 
 inc/registrar/fields/Upload.php                                                                   |   47 
 inc/managers/SEO/render/Traits/_Properties/lifeEventTrait.php                                     |   28 
 inc/registrar/fields/Field.php                                                                    |  143 
 inc/managers/SEO/render/Traits/_Properties/nationalityTrait.php                                   |   23 
 inc/managers/SEO/render/Traits/_Properties/composerTrait.php                                      |   32 
 inc/managers/SEO/render/Traits/_Properties/valueTrait.php                                         |   21 
 inc/managers/SEO/render/Traits/_Properties/honorificSuffixTrait.php                               |   21 
 inc/managers/SEO/render/Traits/_Properties/birthDateTrait.php                                     |   23 
 inc/managers/SEO/render/Traits/_Properties/reviewRatingTrait.php                                  |   23 
 inc/rest/routes/VoteRoutes.php                                                                    |   36 
 src/fields/save.js                                                                                |    0 
 inc/managers/SEO/render/Traits/_Properties/serviceTypeTrait.php                                   |   31 
 inc/managers/SEO/render/Thing/Organization/OnlineBusiness.php                                     |    9 
 inc/managers/SEO/render/Traits/_Properties/productIDTrait.php                                     |   21 
 inc/managers/SEO/render/Traits/_Properties/reviewCountTrait.php                                   |   21 
 inc/managers/SEO/render/Thing/Intangible/StructuredValue/InteractionCounter.php                   |   27 
 inc/managers/SEO/render/Thing/Intangible/StructuredValue/WarrantyPromise.php                      |   14 
 inc/managers/SEO/render/Traits/_Properties/categoryTrait.php                                      |   35 
 inc/managers/SEO/render/Traits/_Properties/validThroughTrait.php                                  |   25 
 inc/managers/SEO/render/Traits/_Properties/familyNameTrait.php                                    |   21 
 inc/managers/SEO/render/Traits/_Properties/addressRegionTrait.php                                 |   23 
 inc/managers/SEO/render/Thing/Intangible/HowToItem.php                                            |   17 
 inc/managers/SEO/render/Traits/_Properties/abstractTrait.php                                      |   21 
 inc/managers/SEO/render/Thing/CreativeWork/WebSite.php                                            |   15 
 inc/managers/SEO/render/Traits/_Properties/mainEntityTrait.php                                    |   24 
 inc/managers/NotificationManager.php                                                              |   18 
 inc/managers/SEO/render/Thing/Intangible/ItemList/BreadcrumbList.php                              |   18 
 inc/managers/SEO/render/Traits/_Properties/productSupportedTrait.php                              |   31 
 inc/managers/SEO/render/Traits/_Properties/dissolutionDateTrait.php                               |   34 
 inc/registry/CheckCustomTables.php                                                                |  157 
 inc/managers/SEO/BreadcrumbManager.php                                                            |   70 
 inc/managers/SEO/render/Thing/Intangible/Reservation/_setup.php                                   |    3 
 inc/managers/SEO/render/Traits/_Properties/honorificPrefixTrait.php                               |   21 
 inc/registrar/Registrar.php                                                                       |  642 +
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/MedicalBusiness/_setup.php               |    2 
 inc/managers/SEO/render/Traits/_Properties/versionTrait.php                                       |   21 
 inc/utility/Validator.php                                                                         |    8 
 inc/registrar/fields/OptionsField.php                                                             |   15 
 inc/managers/SEO/render/Traits/_Properties/timeRequiredTrait.php                                  |   23 
 inc/managers/SEO/render/Traits/_Properties/lastReviewedTrait.php                                  |   23 
 inc/managers/SEO/render/Thing/Intangible/EntryPoint.php                                           |   21 
 inc/managers/SEO/render/Traits/_Properties/materialExtentTrait.php                                |   23 
 inc/managers/SEO/render/Thing/Intangible/Enumeration/Enumeration.php                              |   13 
 inc/managers/SEO/render/Traits/_Properties/childrenTrait.php                                      |   31 
 inc/managers/SEO/render/Thing/Person/Person.php                                                   |   75 
 inc/managers/SEO/render/Traits/_Properties/relatedLinkTrait.php                                   |   21 
 inc/managers/SEO/render/Traits/_Properties/emailTrait.php                                         |   32 
 inc/managers/SEO/render/Traits/_Properties/displayLocationTrait.php                               |   28 
 inc/managers/SEO/render/Traits/_Properties/dunsTrait.php                                          |   21 
 inc/registry/_setup.php                                                                           |   29 
 inc/managers/SEO/render/Thing/Properties/alternativeHeadline.php                                  |   13 
 inc/managers/SEO/render/Traits/_Properties/availabilityStartsTrait.php                            |   25 
 inc/managers/SEO/render/Thing/Product/Product.php                                                 |   61 
 inc/managers/SEO/render/Traits/_Properties/accountablePersonTrait.php                             |   28 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/LocalBusiness.php                        |   21 
 inc/managers/SEO/render/Thing/Thing.php                                                           |   12 
 inc/managers/SEO/render/Traits/_Properties/transFatContentTrait.php                               |   21 
 inc/managers/SEO/render/Traits/_Properties/contentRatingTrait.php                                 |   21 
 inc/managers/SEO/render/Traits/_Properties/predecessorOfTrait.php                                 |   23 
 inc/managers/SEO/render/Traits/_Properties/hasOfferCatalogTrait.php                               |   76 
 inc/managers/SEO/render/Traits/_Properties/priceCurrencyTrait.php                                 |   23 
 inc/managers/SEO/render/Traits/_Properties/userInteractionCountTrait.php                          |   21 
 inc/managers/SEO/render/Traits/_Properties/legalAddressTrait.php                                  |   68 
 inc/managers/SEO/render/Traits/_Properties/resultTrait.php                                        |   23 
 inc/managers/SEO/render/Traits/_Properties/unsaturatedFatContentTrait.php                         |   21 
 inc/managers/SEO/render/Traits/_Properties/providerMobilityTrait.php                              |   21 
 inc/managers/SEO/render/Traits/_Properties/priceTrait.php                                         |   28 
 inc/managers/SEO/render/Thing/CreativeWork/Photograph.php                                         |   10 
 inc/managers/SEO/render/Traits/_Properties/startOffsetTrait.php                                   |   21 
 inc/managers/SEO/render/Traits/_Properties/languageTrait.php                                      |   31 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/IceCreamShop.php       |   11 
 inc/rest/routes/FeedRoutes.php                                                                    |  146 
 inc/managers/SEO/render/Traits/_Properties/remainingAttendeeCapacityTrait.php                     |   21 
 inc/registrar/config/seo/Meta.php                                                                 |   87 
 inc/managers/SEO/render/Traits/_Properties/additionalTypeTrait.php                                |   26 
 inc/managers/SEO/render/Traits/_Properties/recordedInTrait.php                                    |   24 
 inc/managers/SEO/render/Traits/_Properties/reviewBodyTrait.php                                    |   21 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/Brewery.php            |   11 
 inc/managers/SEO/render/Traits/_Properties/objectTrait.php                                        |   23 
 inc/managers/SEO/render/Traits/_Properties/positiveNotesTrait.php                                 |   33 
 inc/managers/SEO/render/Traits/_Properties/disambiguatingDescriptionTrait.php                     |   21 
 inc/managers/SEO/render/Traits/_Properties/materialTrait.php                                      |   31 
 inc/managers/SEO/render/Traits/_Properties/proteinContentTrait.php                                |   21 
 inc/blocks/_setup.php                                                                             |   10 
 inc/registrar/Posts.php                                                                           |  340 
 inc/managers/SEO/render/Thing/Intangible/StructuredValue/TypeAndQuantityNode.php                  |   17 
 inc/managers/SEO/render/Traits/_Properties/logoTrait.php                                          |   33 
 inc/managers/SEO/render/Traits/_Properties/productGroupIDTrait.php                                |   21 
 inc/managers/SEO/render/Traits/_Properties/hasDefinedTermTrait.php                                |   28 
 inc/managers/SEO/render/Traits/_Properties/validInTrait.php                                       |   28 
 inc/managers/queue/executors/UploadExecutor.php                                                   |   18 
 inc/managers/SEO/render/Traits/_Properties/funderTrait.php                                        |   29 
 inc/managers/SEO/render/Traits/_Properties/qualificationsTrait.php                                |   31 
 inc/managers/SEO/render/Traits/_Properties/contributorTrait.php                                   |   32 
 inc/managers/SEO/render/Traits/_Properties/taxIDTrait.php                                         |   21 
 inc/managers/SEO/render/Traits/_Properties/currenciesAcceptedTrait.php                            |   39 
 inc/managers/SEO/render/DataType/DateTime.php                                                     |   42 
 inc/rest/Rest.php                                                                                 |   30 
 src/fields/editor.scss                                                                            |   20 
 inc/managers/SEO/render/Thing/CreativeWork/WebPage/CheckoutPage.php                               |   15 
 inc/managers/SEO/render/Traits/_Properties/worksForTrait.php                                      |   28 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/Library.php                              |   16 
 inc/managers/SEO/render/Traits/_Properties/memberOfTrait.php                                      |   30 
 inc/managers/SEO/render/Thing/Intangible/Quantity/_setup.php                                      |    4 
 inc/rest/routes/NotificationsRoutes.php                                                           |    3 
 inc/managers/SEO/render/Thing/CreativeWork/HowToSection.php                                       |   16 
 inc/managers/SEO/render/Traits/_Properties/requiredQuantityTrait.php                              |   23 
 base/users.php                                                                                    |    4 
 inc/managers/RoleManager.php                                                                      |  171 
 inc/managers/SEO/render/Thing/_setup.php                                                          |   11 
 inc/managers/SEO/render/Traits/_Properties/itemConditionTrait.php                                 |   23 
 inc/managers/SEO/render/Thing/Intangible/Enumeration/ItemAvailability.php                         |   23 
 inc/managers/SEO/render/Traits/_Properties/merchantReturnDaysTrait.php                            |   24 
 inc/managers/SEO/render/Traits/_Properties/reservationForTrait.php                                |   28 
 inc/managers/SEO/render/Traits/_Properties/openingHoursTrait.php                                  |   32 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/HealthAndBeautyBusiness.php              |   16 
 inc/blocks/TimelineBlock.php                                                                      |   13 
 inc/managers/SEO/render/Traits/_Properties/characterTrait.php                                     |   28 
 inc/managers/SEO/render/Traits/_Properties/publishingPrinciplesTrait.php                          |   29 
 inc/managers/IconsManager.php                                                                     |   31 
 inc/managers/SEO/render/Traits/_Properties/elevationTrait.php                                     |   21 
 inc/managers/SEO/render/Traits/_Properties/clipNumberTrait.php                                    |   21 
 inc/managers/SEO/render/Traits/_Properties/endOffsetTrait.php                                     |   21 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/Winery.php             |   11 
 inc/managers/SEO/render/Traits/_Properties/lowPriceTrait.php                                      |   26 
 inc/managers/SEO/render/Traits/_Properties/thumbnailTrait.php                                     |   23 
 inc/managers/SEO/render/Traits/_Properties/itemTrait.php                                          |   23 
 inc/managers/SEO/render/Traits/_Properties/postOfficeBoxNumberTrait.php                           |   21 
 inc/managers/SEO/render/Traits/_Properties/birthPlaceTrait.php                                    |   23 
 inc/rest/routes/FavouritesRoutes.php                                                              |    5 
 inc/managers/SEO/render/Traits/_Properties/eligibleTransactionVolumeTrait.php                     |   23 
 inc/managers/SEO/render/Thing/Intangible/Enumeration/CertificationStatusEnumeration.php           |   17 
 inc/managers/SEO/render/Traits/_Properties/certificationRatingTrait.php                           |   23 
 inc/managers/SEO/render/Traits/_Properties/successorOfTrait.php                                   |   23 
 inc/managers/SEO/render/Traits/_Properties/addressTrait.php                                       |   70 
 inc/managers/SEO/render/Traits/_Properties/httpMethodTrait.php                                    |   21 
 inc/managers/SEO/render/Traits/_Properties/countryOfOriginTrait.php                               |   21 
 inc/managers/SEO/render/Traits/_Properties/encodingFormatTrait.php                                |   25 
 inc/managers/SEO/render/Traits/_Properties/sodiumContentTrait.php                                 |   21 
 inc/managers/queue/executors/ContentTermExecutor.php                                              |   44 
 inc/managers/SEO/render/Traits/_Properties/eligibleRegionTrait.php                                |   33 
 inc/managers/SEO/render/Traits/_Properties/applicableCountryTrait.php                             |   32 
 inc/managers/SEO/render/Thing/Intangible/Enumeration/DigitalPlatformEnumeration.php               |   20 
 inc/managers/SEO/render/Traits/_Properties/paymentAcceptedTrait.php                               |   36 
 inc/managers/SEO/render/Traits/_Properties/servicePhoneTrait.php                                  |   28 
 inc/managers/SEO/render/Traits/_Properties/availableDeliveryMethodTrait.php                       |   23 
 inc/managers/SEO/render/Traits/_Properties/actionPlatformTrait.php                                |   31 
 src/fields/view.js                                                                                |    0 
 inc/managers/SEO/render/Traits/_Properties/maintainerTrait.php                                    |   32 
 inc/managers/SEO/render/Traits/_Properties/membershipPointsEarnedTrait.php                        |   23 
 inc/managers/SEO/render/Thing/Intangible/Service.php                                              |   38 
 inc/managers/SEO/render/Traits/_Properties/acceptsReservationsTrait.php                           |   27 
 inc/managers/SEO/render/Traits/_Properties/isPartOfTrait.php                                      |   24 
 inc/managers/SEO/render/Traits/_Properties/wordCountTrait.php                                     |   21 
 inc/managers/SEO/render/Traits/_Properties/errorTrait.php                                         |   23 
 inc/managers/SEO/render/Traits/_Properties/starRatingTrait.php                                    |   29 
 inc/managers/SEO/render/Thing/CreativeWork/WebPage/RealEstateListing.php                          |   16 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/SportsActivityLocation.php               |   16 
 inc/managers/SEO/render/Traits/_Properties/keywordsTrait.php                                      |   28 
 inc/managers/SEO/render/Traits/_Properties/homeLocationTrait.php                                  |   27 
 inc/managers/SEO/render/DataType/Date.php                                                         |   40 
 inc/managers/SEO/render/Traits/_Properties/contentTypeTrait.php                                   |   27 
 inc/ui/CRUDSkeleton.php                                                                           |   24 
 inc/managers/SEO/render/Traits/_Properties/depthTrait.php                                         |   24 
 inc/managers/SEO/render/Traits/_Properties/hasMeasurementTrait.php                                |   28 
 inc/registry/providers/IntegrationFieldProvider.php                                               |   17 
 inc/managers/SEO/render/Traits/_Properties/heightTrait.php                                        |   24 
 inc/registrar/config/seo/_setup.php                                                               |    7 
 inc/managers/SEO/render/Thing/Intangible/StructuredValue/_setup.php                               |   17 
 inc/managers/SEO/render/Traits/_Properties/contactOptionTrait.php                                 |   23 
 inc/managers/SEO/render/Traits/_Properties/priceValidUntilTrait.php                               |   23 
 inc/managers/SEO/render/Thing/CreativeWork/WebPage/AboutPage.php                                  |   14 
 inc/managers/SEO/render/Traits/_Properties/awardTrait.php                                         |   42 
 inc/managers/SEO/render/Traits/_Properties/jobTitleTrait.php                                      |   31 
 inc/managers/SEO/render/Traits/_Properties/sizeTrait.php                                          |   25 
 inc/registrar/helpers/AddIntegrationFields.php                                                    |  135 
 inc/managers/SEO/render/Traits/_Properties/performerTrait.php                                     |   26 
 inc/managers/SEO/render/Traits/_Properties/ratingCountTrait.php                                   |   21 
 inc/managers/SEO/render/Traits/_Properties/fiberContentTrait.php                                  |   21 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/GovernmentOffice.php                     |   16 
 inc/managers/SEO/render/Traits/_Properties/availableLanguageTrait.php                             |   28 
 inc/registrar/config/Config.php                                                                   |   10 
 inc/managers/SEO/render/Thing/CreativeWork/WebPage/FAQPage.php                                    |   15 
 inc/managers/SEO/render/Traits/_Properties/itemOfferedTrait.php                                   |   40 
 inc/managers/SEO/render/Traits/_Properties/affiliationTrait.php                                   |   28 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/CafeOrCoffeeShop.php   |   11 
 inc/admin/Integrations.php                                                                        |    5 
 inc/registrar/fields/TaxonomyField.php                                                            |   19 
 inc/registrar/config/SEO.php                                                                      |   67 
 inc/managers/SEO/render/Thing/CreativeWork/CategoryCodeSet.php                                    |   12 
 inc/managers/SEO/render/Traits/_Properties/serviceUrlTrait.php                                    |   25 
 inc/managers/InvitationsManager.php                                                               |   84 
 inc/managers/SEO/render/Thing/Intangible/StructuredValue/OpeningHoursSpecification.php            |   16 
 inc/managers/SEO/render/Thing/Intangible/Enumeration/BusinessFunction.php                         |   23 
 inc/managers/SEO/render/Traits/_Properties/fundedItemTrait.php                                    |   39 
 inc/managers/SEO/render/Traits/_Properties/significantLinkTrait.php                               |   21 
 inc/managers/SEO/render/Traits/_Properties/makesOfferTrait.php                                    |   28 
 inc/managers/SEO/render/Traits/_Properties/servicePostalAddressTrait.php                          |   23 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/HomeAndConstructionBusiness.php          |   16 
 inc/managers/SEO/render/Thing/Organization/LocalBusiness/DryCleaningOrLaundry.php                 |   16 
 inc/managers/SEO/render/Thing/Intangible/StructuredValue/GeoShape.php                             |   20 
 inc/managers/SEO/render/Traits/_Properties/offeredByTrait.php                                     |   33 
 inc/managers/SEO/render/Thing/Intangible/Enumeration/AdultOrientedEnumeration.php                 |   25 
 inc/managers/SEO/render/Traits/_Properties/actorTrait.php                                         |   29 
 inc/managers/SEO/render/Thing/Intangible/Rating/AggregateRating.php                               |   16 
 inc/managers/SEO/render/Thing/Intangible/StructuredValue/ContactPoint.php                         |   22 
 inc/managers/SEO/render/Traits/_Properties/creatorTrait.php                                       |   32 
 inc/managers/SEO/render/DataType/_setup.php                                                       |    4 
 inc/managers/SEO/render/Traits/_Properties/businessFunctionTrait.php                              |   23 
 inc/managers/SEO/render/Traits/_Properties/valueReferenceTrait.php                                |   27 
 inc/managers/SEO/render/Traits/_Properties/copyrightHolderTrait.php                               |   32 
 inc/managers/SEO/render/Traits/_Properties/hasCredentialTrait.php                                 |   34 
 inc/managers/SEO/render/Traits/_Properties/inStoreReturnsOfferedTrait.php                         |   21 
 inc/managers/SEO/render/Traits/_Properties/maximumPhysicalAttendeeCapacityTrait.php               |   21 
 inc/managers/SEO/render/Thing/CreativeWork/WebPage/_setup.php                                     |   13 
 inc/managers/SEO/render/Traits/_Properties/serviceSmsNumberTrait.php                              |   28 
 assets/js/min/crud.min.js                                                                         |    2 
 inc/managers/SEO/render/Traits/_Properties/nextItemTrait.php                                      |   23 
 inc/managers/SEO/render/Traits/_Properties/hasMenuTrait.php                                       |   32 
 inc/managers/SEO/render/Traits/_Properties/ownershipFundInfoTrait.php                             |   32 
 inc/managers/SEO/render/Thing/CreativeWork/WebPage/ProfilePage.php                                |   22 
 src/fields/style.scss                                                                             |   20 
 inc/managers/SEO/render/Traits/_Properties/workExampleTrait.php                                   |   24 
 inc/managers/SEO/render/Thing/Properties/about.php                                                |   17 
 inc/managers/SEO/render/Traits/_Properties/cutoffTimeTrait.php                                    |   23 
 inc/registrar/config/Feed.php                                                                     |   83 
 inc/managers/SEO/render/Thing/Intangible/ContactPoint/PostalAddress.php                           |   20 
 inc/managers/SEO/render/Traits/_Properties/merchantReturnLinkTrait.php                            |   25 
 inc/managers/SEO/render/Traits/_Properties/latitudeTrait.php                                      |   30 
 src/fields/render.php                                                                             |  320 
 inc/managers/SEO/render/Traits/_Properties/startTimeTrait.php                                     |   26 
 inc/managers/SEO/render/Thing/Intangible/Enumeration/ContactPointOption.php                       |   14 
 inc/managers/SEO/render/Traits/_Properties/estimateCostTrait.php                                  |   23 
 inc/managers/SEO/render/Traits/_Properties/faxNumberTrait.php                                     |   27 
 /dev/null                                                                                         |   46 
 inc/managers/SEO/render/Traits/_Properties/directorTrait.php                                      |   24 
 770 files changed, 22,594 insertions(+), 1,997 deletions(-)

diff --git a/JVBase.php b/JVBase.php
index 3de959a..c5f383f 100644
--- a/JVBase.php
+++ b/JVBase.php
@@ -15,11 +15,13 @@
 use JVBase\managers\ReferralManager;
 use JVBase\managers\RoleManager;
 //use JVBase\managers\SchemaManager;
+use JVBase\managers\SEO\render\SchemaOutput;
 use JVBase\managers\SEO\SchemaOutputManager;
-use JVBase\managers\SEO\SEOAdminPage;
+use JVBase\admin\SEOAdmin;
 use JVBase\managers\AdminPages;
 use JVBase\managers\NotificationManager;
 use JVBase\managers\UserTermsManager;
+use JVBase\registrar\Registrar;
 use JVBase\rest\routes\FeedRoutes;
 use JVBase\rest\routes\FavouritesRoutes;
 use JVBase\rest\routes\IntegrationsSquareRoutes;
@@ -90,17 +92,18 @@
 	{
 		$this->customBlocks = new CustomBlocks();
 		$this->managers = [
-			'errors' => new ErrorHandler(),
-			'queue' => new Queue(),
-//            'dash'          => new DashboardManager(),
-			'roles' => new RoleManager(),
-//            'forms'         => new FormManager(),
-			'schema' => new SchemaOutputManager(),
-			'admin' => new AdminPages(),
-			'seoAdmin' => new SEOAdminPage(),
-//			'uploads'		=> new UploadManager(),
+			'errors' 	=> new ErrorHandler(),
+			'queue' 	=> new Queue(),
+//            'dash'    => new DashboardManager(),
+			'roles' 	=> new RoleManager(),
+//            'forms'   => new FormManager(),
+//			'schema' 	=> new SchemaOutputManager(),
+			'admin' 	=> new AdminPages(),
+			'seoAdmin' 	=> new SEOAdmin(),
+			'seo'		=> new SchemaOutput(),
+//			'uploads'	=> new UploadManager(),
 			'userTerms' => new UserTermsManager(),
-			'email' => new EmailManager(),
+			'email' 	=> new EmailManager(),
 		];
 
 		$this->routes = [
@@ -136,11 +139,11 @@
 		if (Features::forSite()->has('feed_block')) {
 			$this->routes['feed'] = new FeedRoutes();
 		}
-		if (jvbSiteHasNotifications()) {
+		if (Features::forMembership()->has('notifications')) {
 			$this->managers['notifications'] = new NotificationManager();
 			$this->routes['notifications'] = new NotificationsRoutes();
 		}
-		if (Features::forSite()->has('feed_block') || jvbSiteHasDashboard()) {
+		if (Features::forSite()->has('feed_block') || Features::forSite()->has('dashboard')) {
 			$this->routes['term'] = new TermRoutes();
 		}
 
@@ -148,7 +151,7 @@
 			$this->managers['directory'] = new DirectoryManager();
 		}
 
-		if (jvbSiteHasDashboard()) {
+		if (Features::forSite()->has('dashboard')) {
 			$this->routes['error'] = new ErrorRoutes();
             $this->routes['admin']  = new AdminRoutes();
 			$this->routes['content'] = new ContentRoutes();
@@ -158,7 +161,7 @@
 			$this->routes['options'] = new OptionsRoutes();
 		}
 
-		if (jvbSiteHasFavourites()) {
+		if (Features::forSite()->has('favourites')) {
 			$this->routes['favourites'] = new FavouritesRoutes();
 		}
 
@@ -168,13 +171,13 @@
 		if (Features::forMembership()->has('invitable')) {
 			$this->managers['invitations'] = new InvitationsManager();
 		}
-		if (Features::anyContentHas('response') || Features::anyTaxonomyHas('response') || Features::anyUserHas('response')) {
+		if (!empty(Registrar::getFeatured('has_responses'))) {
 			$this->routes['comments'] = new ResponseRoutes();
 		}
-		if (Features::anyContentHas('karma') || Features::anyTaxonomyHas('karma') || Features::anyUserHas('karma')) {
+		if (!empty(Registrar::getFeatured('karma'))) {
 			$this->routes['vote'] = new VoteRoutes();
 		}
-		if (Features::anyContentHas('karma') || Features::anyTaxonomyHas('karma') || Features::anyUserHas('karma')
+		if (!empty(Registrar::getFeatured('karma'))
 			|| Features::forMembership()->has('member_verified') ||
 			Features::forMembership()->has('term_approval')) {
 			$this->routes['approvals'] = new ApprovalRoutes();
@@ -270,8 +273,7 @@
 
 	public function getFields($type): array
 	{
-		$content = JVB_CONTENT[$type] ?? JVB_TAXONOMY[$type] ?? JVB_USER[$type] ?? [];
-		return $content['fields'] ?? [];
+		return Registrar::getFieldsFor($type)??[];
 	}
 
 	public function getContent($type): mixed
@@ -370,6 +372,11 @@
 		}
 	}
 
+	public function seo():SchemaOutput
+	{
+		return $this->managers['seo'];
+	}
+
 	public function blocks():CustomBlocks|bool
 	{
 		return $this->customBlocks??false;
diff --git a/activate.php b/activate.php
index 8287709..a87076f 100644
--- a/activate.php
+++ b/activate.php
@@ -2,9 +2,13 @@
 
 use JVBase\integrations\Umami;
 use JVBase\managers\Cache;
+use JVBase\managers\CustomTable;
 use JVBase\managers\DirectoryManager;
+use JVBase\managers\queue\Queue;
 use JVBase\managers\ReferralManager;
+use JVBase\managers\RoleManager;
 use JVBase\managers\SEO\SEOAdminPage;
+use JVBase\registrar\Registrar;
 use JVBase\utility\Features;
 
 if (!defined('ABSPATH')) {
@@ -13,21 +17,24 @@
 
 function jvbActivatePlugin():void
 {
-	$validator = new JVBase\utility\Validator();
-	$validation = $validator->validateAll();
-	error_log('Validation result: '.print_r($validation, true));
-	$checker = JVBase\utility\Checker::getInstance();
-	error_log('Doing our activation action...');
+
+//	$validator = new JVBase\utility\Validator();
+//	$validation = $validator->validateAll();
+//	error_log('Validation result: '.print_r($validation, true));
+//	$checker = JVBase\utility\Checker::getInstance();
+//	error_log('Doing our activation action...');
 	// Get ContentRegistry instance early
-	$registry = new JVBase\registry\ContentRegistry();
+//	$registry = new JVBase\registry\ContentRegistry();
 	// Initialize content types
-	$registry->onActivation();
-	$checker->invalidateContentCache();
-	$checker->invalidateTaxonomyCache();
+//	$registry->onActivation();
+//	$checker->invalidateContentCache();
+//	$checker->invalidateTaxonomyCache();
     do_action(BASE.'activation');
 	error_log('Action done!');
 	error_log('Checking custom tables...');
-    (new JVBase\registry\CheckCustomTables())->maybeCreateTables();
+	Queue::defineTables();
+	CustomTable::ensureTables();
+//    (new JVBase\registry\CheckCustomTables())->maybeCreateTables();
 	error_log('Tables created!');
 
     // Store schema version
@@ -36,7 +43,9 @@
     jvbSchedules();
 	error_log('Schedules done!');
 	error_log('checking Admin capabilities...');
-    jvbAddAdminCaps();
+
+	jvb_register_do_once('admin_caps', 'jvbAddAdminCaps');
+
 	error_log('Admin caps done!');
 	error_log('Removing unneeded roles...');
     remove_role('contributor');
@@ -50,22 +59,23 @@
 		Umami::createTables();
 	}
 
-	if (Features::forSite()->has('is_directory')) {
-		error_log('Activating DirectoryManager');
-		DirectoryManager::activate();
-	}
+//	if (Features::forSite()->has('is_directory')) {
+//		error_log('Activating DirectoryManager');
+//		jvb_register_do_once('buildDirectory', ['JVBase\managers\DirectoryManager', 'activate']);
+//	}
 	error_log('Activation done! Huzzah!');
 }
 
 function jvbAddAdminCaps()
 {
-	$roleManager = new \JVBase\managers\RoleManager();
+	$roleManager = new RoleManager();
+
 
 	$role = get_role('administrator');
 	$users = get_users(['role' => 'administrator']);
-    foreach (JVB_CONTENT as $slug => $config) {
-
-		$plural = strtolower($config['plural']);
+    foreach (array_merge(Registrar::getRegistered('post'), Registrar::getFeatured('is_content')) as $slug) {
+		error_log('Adding administrative roles to '.$slug);
+		$plural = $roleManager->getContentPlural($slug);
 		$capabilities = [
 			'edit_' . $plural,
 			'edit_published_' . $plural,
@@ -97,14 +107,15 @@
 function jvbGrantAdminUserCaps()
 {
 	// Only run once after activation
-	if (get_option('jvb_admin_caps_granted') === '1') {
+	if (get_option(BASE.'admin_caps_granted') === '1') {
 		return;
 	}
 
-	$roleManager = new \JVBase\managers\RoleManager();
+	$roleManager = new RoleManager();
 	$users = get_users(['role' => 'administrator']);
 
-	foreach (JVB_CONTENT as $slug => $config) {
+	foreach (array_merge(Registrar::getRegistered('post'), Registrar::getFeatured('is_content', 'term')) as $slug) {
+
 		foreach ($users as $user) {
 			// These methods should check if post type exists before adding caps
 			$roleManager->grantContent($user, $slug);
@@ -113,7 +124,7 @@
 	}
 
 	// Mark as complete to prevent running again
-	update_option('jvb_admin_caps_granted', '1');
+	update_option(BASE.'admin_caps_granted', '1');
 	remove_action('init', 'jvbGrantAdminUserCaps', 99);
 }
 function jvbScheduledHooks()
@@ -269,5 +280,5 @@
 	if (Features::forSite()->has('referrals')){
 		ReferralManager::addSubpage();
 	}
-	SEOAdminPage::addSubpage();
+//	SEOAdminPage::addSubpage();
 }
diff --git a/assets/css/nav.min.css b/assets/css/nav.min.css
index f063fae..07c23b2 100644
--- a/assets/css/nav.min.css
+++ b/assets/css/nav.min.css
@@ -1 +1 @@
-nav,nav ol,nav ul{--padding:0 1rem;--wrap:nowrap;display:flex;flex-direction:var(--dir,row);justify-content:var(--justify,flex-start);align-items:var(--align,center);gap:var(--gap,0);flex-wrap:var(--wrap,nowrap);height:var(--btn,3rem);max-width:100%;font-family:var(--heading);padding:0;margin:0}nav li{display:flex;align-items:center;height:max(var(--btn),max-content);width:100%;max-inline-size:none;padding:0}nav a,nav button{display:flex;text-decoration:none;align-items:center;justify-content:center;height:var(--btn);width:100%;white-space:nowrap;text-transform:uppercase;transition:var(--trans-color)}nav a{height:var(--btn);padding:var(--padding)}nav button{justify-content:center;aspect-ratio:1;padding:0;border:2px solid var(--base);color:var(--contrast);border-radius:0}nav .current a,nav a.current,nav a:focus,nav a:focus:visited,nav a:hover,nav button:focus{background-color:var(--action-0);color:var(--action-contrast)}.toggle .icon{transform:rotate(0);transition:transform var(--trans-base)}.has-submenu.open>button .icon{transform:rotate(900deg)}.has-submenu{position:relative}ul.submenu{--dir:column;height:max-content;position:absolute;top:100%;left:0;max-height:0;transform:scaleY(0);transform-origin:top;width:max(100%,max-content);background-color:rgba(var(--base-rgb),var(--op-3));border:2px solid rgba(var(--base-rgb),var(--op-3));transition:all var(--trans-t) var(--trans-fn);box-shadow:var(--shdw-none);overflow:hidden}.submenu li{background-color:rgba(var(--base-rgb),var(--op-6));border:1px solid var(--base-50)}.open>ul.submenu{transform:scaleY(1);max-height:1000%;box-shadow:rgba(var(--base-rgb),var(--op-45)) var(--shdw)}.screen-reader-text{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}nav a:focus:not(:focus-visible){outline:0}nav a:focus-visible{outline:2px solid var(--action-0);outline-offset:2px}nav.always{--dir:column;--wrap:nowrap;position:fixed;bottom:0;right:0;width:var(--btn);z-index:var(--z-10)}nav.always.open{--justify:flex-end;width:100vw;height:100vh;padding-bottom:var(--btn_);background-color:rgba(var(--base-rgb),var(--op-6));backdrop-filter:blur(5px)}nav.always>ul{--dir:column;--align:center;--justify:flex-start;--gap:0;height:100%;position:relative;right:-300vw;width:100vw;max-height:100%;padding:1rem 0 0;overflow:hidden auto;transition:right var(--trans-base)}nav.always.open>ul{right:0}nav.always li{flex-wrap:wrap;background-color:rgba(var(--base-rgb),var(--op-6))}nav.always a{padding:1rem;max-width:calc(100% - var(--btn));text-align:center}nav.always .has-submenu{display:flex}nav.always .has-submenu>a{flex:1}nav.always .has-submenu>button{flex:0 0 var(--btn)}nav.always .submenu{position:relative;padding-right:4rem;height:max-content;top:0;width:100%;border:2px solid var(--action-0);background-color:rgba(var(--contrast-rgb),var(--op-1))}nav.always .submenu li{background-color:rgba(var(--base-rgb),var(--op-3))}nav.always>button{position:fixed;bottom:0;right:0;width:var(--btn);height:var(--btn);background-color:var(--base);color:var(--contrast);box-shadow:rgba(var(--base-rgb),var(--op-45)) var(--shdw);transition:width var(--trans-base)}nav.always>button:hover{background-color:var(--action-0);color:var(--action-contrast)}nav.always.open>button{width:100%;background-color:rgba(var(--base-rgb),var(--op-6));backdrop-filter:blur(5px);z-index:1000000}nav.always.open>button .icon-list,nav.always>button .icon-x{display:none}nav.always.open>button .icon-x,nav.always>button .icon-list{display:block;width:32px;height:32px}@media (min-width:768px){nav.always>ul{padding-top:var(--btn)}}nav#breadcrumbs{height:max-content;--wrap:wrap;--gap:0;width:max-content;max-width:var(--full);position:absolute;background-color:rgba(var(--base-rgb),var(--op-4));font-size:var(--txt-x-small);padding:.125em;z-index:var(--z-7)}#breadcrumbs ol{height:max-content}#breadcrumbs li{width:max-content}#breadcrumbs a{height:var(--chip)}#breadcrumbs li::after{content:'/';color:var(--contrast-200);padding:0 .25rem}#breadcrumbs li:last-of-type::after{display:none}#breadcrumbs :is(a,span){padding:0 .125rem;color:var(--contrast);text-transform:none}#breadcrumbs a:focus{background-color:transparent;color:var(--action-0)}nav.fixed.bottom{position:fixed;bottom:0;left:0;width:calc(100% - var(--btn));box-shadow:rgba(var(--base-rgb),var(--op-45)) var(--shdw);z-index:var(--z-9)}nav.fixed.bottom ul{--justify:space-between;width:100%;background-color:var(--base);padding:0 .25rem}nav.fixed.bottom li{flex:1;justify-content:center}nav.fixed.bottom a{gap:1rem;--w:var(--chip_);color:var(--contrast);font-size:var(--txt-x-small)}@media (min-width:768px){nav.fixed.bottom a{font-size:var(--txt-medium)}}nav.on-this-page{--justify:space-between;position:fixed;bottom:0;left:0;width:calc(100% - var(--btn));max-width:none;padding:0 .5rem;background-color:rgba(var(--base-rgb),var(--op-4));color:var(--base-200);box-shadow:rgba(var(--base-rgb),var(--op-45)) var(--shdw);z-index:var(--z-6)}body:has(nav.fixed) nav.on-this-page{bottom:var(--btn)}body:has(.additional-actionsbutton) nav.on-this-page{width:calc(100vw - var(--btn_) - 1rem)}.on-this-page ul{width:100%;gap:0}.on-this-page li{justify-content:center}.on-this-page .active a{background-color:rgba(var(--base-rgb),var(--op-6));color:var(--action-contrast)}.on-this-page a{height:var(--chip);padding:0}nav.letters li{height:var(--chip);max-width:calc(7.69% - 2px)}nav.letters ul{--wrap:wrap}nav.letters,nav.letters ul{height:var(--chipchip)}@media (min-width:768px){nav.letters,nav.letters ul{height:var(--chip)}nav.letters ul{--wrap:nowrap}nav.letters li{max-width:none}nav.letters a{padding:.25rem .66rem}}nav.index{--justify:flex-start;--padding:0;background-color:rgba(var(--base-rgb),var(--op-6))}.index ul{width:max-content}.index li{flex-shrink:0;transform:scaleX(0);transform-origin:right;max-width:0;overflow:hidden;transition:transform var(--trans-base)}.index li.active,.index li.adj{transform:scaleX(1);transform-origin:left;width:100%;flex-shrink:1;max-width:fit-content}@media (max-width:767px){.index li.adj{transform:scaleX(0);max-width:0}}.index a{border-bottom:4px solid transparent}.index .active a{border-color:var(--action-0);color:var(--contrast)}.index.open{--dir:column-reverse;height:var(--maxHeight);width:100%;align-items:flex-end;background-color:rgba(var(--base-rgb),var(--op-6));backdrop-filter:blur(5px);z-index:var(--z-10)}.index.open ul{--dir:column;--justify:flex-end;height:100%;width:100%}.index.open li{width:100%;height:var(--btn);max-width:100%!important;transform:scaleX(1);overflow:visible}.index.open a{justify-content:flex-end;padding:0 2rem 0 0;background-color:transparent}nav.condensed{height:max-content;--wrap:wrap;--gap:0 .25rem}nav.condensed ul{min-height:var(--chip_);height:max-content;--justify:center;--wrap:wrap}.condensed li{width:max-content;min-height:var(--chip)}.condensed li+li::before{content:'·';padding:0 .25em}.condensed a{height:max-content;min-height:var(--chip);font-size:var(--txt-x-small);padding:0 .25rem;text-transform:none;border-bottom:2px solid transparent}.condensed a:focus{border-color:var(--action-0)}ul.socials{--dir:row;height:max-content;--gap:.5rem;--justify:stretch;--wrap:nowrap;overflow:auto hidden;touch-action:pan-x}.always ul.socials,.always ul.socials a,.always ul.socials li{width:100%}ul.socials a{padding:.5rem;max-width:none}ul.socials .icon{margin:0}nav.tabs{position:fixed;bottom:var(--btn);left:var(--btnbtn);right:var(--btnbtn);padding-bottom:2px;z-index:var(--z-6);touch-action:pan-x pan-y;--wrap:nowrap;overflow:auto hidden}nav.tabs button{aspect-ratio:unset}nav.tabs button.active{cursor:default}nav.tabs button.active:hover{background-color:var(--base-100);color:var(--contrast)}nav.tabs button h2{--wrap:nowrap;margin:0;font-size:var(--txt-x-small)}.tab-content nav.tabs button{height:var(--chip_);padding:.25rem .75rem;min-height:0}.tab-content.active{padding:1rem 0}.tab-content h2{margin:0 0 .5rem}.tab-content nav.tabs{height:max-content;background-color:var(--base);--gap:0}.tab-content .tab-content nav.tabs{background-color:var(--base-100)}.tab-content .tab-content .tab-content nav.tabs{background-color:var(--base-200)}.tab-content nav.tabs button.active h2{color:var(--action-0)}nav.menu a{padding:.5rem .66rem}nav.share{height:max-content;margin:1rem 0}nav.share ul{overflow:visible}nav.share h4{display:inline-block;width:max-content;margin:.25rem .5rem .25rem 0;font-size:var(--txt-x-small)}:where(body>header,.wp-site-blocks>header){--dir:row;--justify:space-between;position:sticky;top:0;left:0;right:0;height:var(--btn);width:100vw;display:flex;align-items:center;padding:0 .5rem;background-color:var(--base);box-shadow:rgba(var(--base-rgb),var(--op-45)) var(--shdw);z-index:var(--z-9)}.wp-site-blocks>header img{width:var(--btn)}nav.term-navigation:has([hidden]){display:none}.dashboard-nav{--justify:flex-start;width:100%}nav.filters{--dir:row;--justify:flex-start;overflow:auto hidden}nav.filters .filter{width:auto;padding:.25rem .75rem}
\ No newline at end of file
+nav,nav ol,nav ul{--padding:0 1rem;--wrap:nowrap;display:flex;flex-direction:var(--dir,row);justify-content:var(--justify,flex-start);align-items:var(--align,center);gap:var(--gap,0);flex-wrap:var(--wrap,nowrap);height:var(--btn,3rem);max-width:100%;font-family:var(--heading);padding:0;margin:0}nav li{display:flex;align-items:center;height:max(var(--btn),max-content);width:100%;max-inline-size:none;padding:0}nav a,nav button{display:flex;text-decoration:none;align-items:center;justify-content:center;height:var(--btn);width:100%;white-space:nowrap;text-transform:uppercase;transition:var(--trans-color)}nav a{height:var(--btn);padding:var(--padding)}nav button{justify-content:center;aspect-ratio:1;padding:0;border:2px solid var(--base);color:var(--contrast);border-radius:0}nav .current a,nav a.current,nav a:focus,nav a:focus:visited,nav a:hover,nav button:focus{background-color:var(--action-0);color:var(--action-contrast)}.toggle .icon{transform:rotate(0);transition:transform var(--trans-base)}.has-submenu.open>button .icon{transform:rotate(900deg)}.has-submenu{position:relative}ul.submenu{--dir:column;height:max-content;position:absolute;top:100%;left:0;max-height:0;transform:scaleY(0);transform-origin:top;width:max(100%,max-content);background-color:rgba(var(--base-rgb),var(--op-3));border:2px solid rgba(var(--base-rgb),var(--op-3));transition:all var(--trans-t) var(--trans-fn);box-shadow:var(--shdw-none);overflow:hidden}.submenu li{background-color:rgba(var(--base-rgb),var(--op-6));border:1px solid var(--base-50)}.open>ul.submenu{transform:scaleY(1);max-height:1000%;box-shadow:rgba(var(--base-rgb),var(--op-45)) var(--shdw)}.screen-reader-text{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}nav a:focus:not(:focus-visible){outline:0}nav a:focus-visible{outline:2px solid var(--action-0);outline-offset:2px}nav.always{--dir:column;--wrap:nowrap;position:fixed;bottom:0;right:0;width:var(--btn);z-index:var(--z-10)}nav.always.open{--justify:flex-end;width:100vw;height:100vh;padding-bottom:var(--btn_);background-color:rgba(var(--base-rgb),var(--op-6));backdrop-filter:blur(5px)}nav.always>ul{--dir:column;--align:center;--justify:flex-start;--gap:0;height:100%;position:relative;right:-300vw;width:100vw;max-height:100%;padding:1rem 0 0;overflow:hidden auto;transition:right var(--trans-base)}nav.always.open>ul{right:0}nav.always li{flex-wrap:wrap;background-color:rgba(var(--base-rgb),var(--op-6))}nav.always a{padding:1rem;max-width:calc(100% - var(--btn));text-align:center}nav.always .has-submenu{display:flex}nav.always .has-submenu>a{flex:1}nav.always .has-submenu>button{flex:0 0 var(--btn)}nav.always .submenu{position:relative;padding-right:4rem;height:max-content;top:0;width:100%;border:2px solid var(--action-0);background-color:rgba(var(--contrast-rgb),var(--op-1))}nav.always .submenu li{background-color:rgba(var(--base-rgb),var(--op-3))}nav.always>button{position:fixed;bottom:0;right:0;width:var(--btn);height:var(--btn);background-color:var(--base);color:var(--contrast);box-shadow:rgba(var(--base-rgb),var(--op-45)) var(--shdw);transition:width var(--trans-base)}nav.always>button:hover{background-color:var(--action-0);color:var(--action-contrast)}nav.always.open>button{width:100%;background-color:rgba(var(--base-rgb),var(--op-6));backdrop-filter:blur(5px);z-index:1000000}nav.always.open>button .icon-list,nav.always>button .icon-x{display:none}nav.always.open>button .icon-x,nav.always>button .icon-list{display:block;width:32px;height:32px}@media (min-width:768px){nav.always>ul{padding-top:var(--btn)}}nav#breadcrumbs{height:max-content;--wrap:wrap;--gap:0;width:max-content;max-width:var(--full);position:absolute;background-color:rgba(var(--base-rgb),var(--op-4));font-size:var(--txt-x-small);padding:.125em;z-index:var(--z-7)}#breadcrumbs ol{height:max-content;--wrap:wrap!important;--justify:flex-start!important}#breadcrumbs li{width:max-content}#breadcrumbs a{height:var(--chip)}#breadcrumbs li::after{content:'/';color:var(--contrast-200);padding:0 .25rem}#breadcrumbs li:last-of-type::after{display:none}#breadcrumbs :is(a,span){padding:0 .125rem;color:var(--contrast);text-transform:none}#breadcrumbs a:focus{background-color:transparent;color:var(--action-0)}nav.fixed.bottom{position:fixed;bottom:0;left:0;width:calc(100% - var(--btn));box-shadow:rgba(var(--base-rgb),var(--op-45)) var(--shdw);z-index:var(--z-9)}nav.fixed.bottom ul{--justify:space-between;width:100%;background-color:var(--base);padding:0 .25rem}nav.fixed.bottom li{flex:1;justify-content:center}nav.fixed.bottom a{gap:1rem;--w:var(--chip_);color:var(--contrast);font-size:var(--txt-x-small)}@media (min-width:768px){nav.fixed.bottom a{font-size:var(--txt-medium)}}nav.on-this-page{--justify:space-between;position:fixed;bottom:0;left:0;width:calc(100% - var(--btn));max-width:none;padding:0 .5rem;background-color:rgba(var(--base-rgb),var(--op-4));color:var(--base-200);box-shadow:rgba(var(--base-rgb),var(--op-45)) var(--shdw);z-index:var(--z-6)}body:has(nav.fixed) nav.on-this-page{bottom:var(--btn)}body:has(.additional-actionsbutton) nav.on-this-page{width:calc(100vw - var(--btn_) - 1rem)}.on-this-page ul{width:100%;gap:0}.on-this-page li{justify-content:center}.on-this-page .active a{background-color:rgba(var(--base-rgb),var(--op-6));color:var(--action-contrast)}.on-this-page a{height:var(--chip);padding:0}nav.letters li{height:var(--chip);max-width:calc(7.69% - 2px)}nav.letters ul{--wrap:wrap}nav.letters,nav.letters ul{height:var(--chipchip)}@media (min-width:768px){nav.letters,nav.letters ul{height:var(--chip)}nav.letters ul{--wrap:nowrap}nav.letters li{max-width:none}nav.letters a{padding:.25rem .66rem}}nav.index{--justify:flex-start;--padding:0;background-color:rgba(var(--base-rgb),var(--op-6))}.index ul{width:max-content}.index li{flex-shrink:0;transform:scaleX(0);transform-origin:right;max-width:0;overflow:hidden;transition:transform var(--trans-base)}.index li.active,.index li.adj{transform:scaleX(1);transform-origin:left;width:100%;flex-shrink:1;max-width:fit-content}@media (max-width:767px){.index li.adj{transform:scaleX(0);max-width:0}}.index a{border-bottom:4px solid transparent}.index .active a{border-color:var(--action-0);color:var(--contrast)}.index.open{--dir:column-reverse;height:var(--maxHeight);width:100%;align-items:flex-end;background-color:rgba(var(--base-rgb),var(--op-6));backdrop-filter:blur(5px);z-index:var(--z-10)}.index.open ul{--dir:column;--justify:flex-end;height:100%;width:100%}.index.open li{width:100%;height:var(--btn);max-width:100%!important;transform:scaleX(1);overflow:visible}.index.open a{justify-content:flex-end;padding:0 2rem 0 0;background-color:transparent}nav.condensed{height:max-content;--wrap:wrap;--gap:0 .25rem}nav.condensed ul{min-height:var(--chip_);height:max-content;--justify:center;--wrap:wrap}.condensed li{width:max-content;min-height:var(--chip)}.condensed li+li::before{content:'·';padding:0 .25em}.condensed a{height:max-content;min-height:var(--chip);font-size:var(--txt-x-small);padding:0 .25rem;text-transform:none;border-bottom:2px solid transparent}.condensed a:focus{border-color:var(--action-0)}ul.socials{--dir:row;height:max-content;--gap:.5rem;--justify:stretch;--wrap:nowrap;overflow:auto hidden;touch-action:pan-x}.always ul.socials,.always ul.socials a,.always ul.socials li{width:100%}ul.socials a{padding:.5rem;max-width:none}ul.socials .icon{margin:0}nav.tabs{position:fixed;bottom:var(--btn);left:var(--btnbtn);right:var(--btnbtn);padding-bottom:2px;z-index:var(--z-6);touch-action:pan-x pan-y;--wrap:nowrap;overflow:auto hidden}nav.tabs button{aspect-ratio:unset}nav.tabs button.active{cursor:default}nav.tabs button.active:hover{background-color:var(--base-100);color:var(--contrast)}nav.tabs button h2{--wrap:nowrap;margin:0;font-size:var(--txt-x-small)}.tab-content nav.tabs button{height:var(--chip_);padding:.25rem .75rem;min-height:0}.tab-content.active{padding:1rem 0}.tab-content h2{margin:0 0 .5rem}.tab-content nav.tabs{height:max-content;background-color:var(--base);--gap:0}.tab-content .tab-content nav.tabs{background-color:var(--base-100)}.tab-content .tab-content .tab-content nav.tabs{background-color:var(--base-200)}.tab-content nav.tabs button.active h2{color:var(--action-0)}nav.menu a{padding:.5rem .66rem}nav.share{height:max-content;margin:1rem 0}nav.share ul{overflow:visible}nav.share h4{display:inline-block;width:max-content;margin:.25rem .5rem .25rem 0;font-size:var(--txt-x-small)}:where(body>header,.wp-site-blocks>header){--dir:row;--justify:space-between;position:sticky;top:0;left:0;right:0;height:var(--btn);width:100vw;display:flex;align-items:center;padding:0 .5rem;background-color:var(--base);box-shadow:rgba(var(--base-rgb),var(--op-45)) var(--shdw);z-index:var(--z-9)}.wp-site-blocks>header img{width:var(--btn)}nav.term-navigation:has([hidden]){display:none}.dashboard-nav{--justify:flex-start;width:100%}nav.filters{--dir:row;--justify:flex-start;overflow:auto hidden}nav.filters .filter{width:auto;padding:.25rem .75rem}
\ No newline at end of file
diff --git a/assets/js/concise/CRUD.js b/assets/js/concise/CRUD.js
index a189ac9..a4e1573 100644
--- a/assets/js/concise/CRUD.js
+++ b/assets/js/concise/CRUD.js
@@ -798,13 +798,22 @@
 	}
 
 	handleItemUpdate(e) {
-
 		let item = window.targetCheck(e, '[data-item-id]');
 		if (!item) return;
 
-		let field = e.target.closest('[data-field-type="repeater"],[data-field-type="tag-list"],[data-field]');
-		let name = field.dataset.field;
-		let value = this.forms.getFieldValue(e.target);
+		// Check if inside a collection field first
+		const collection = e.target.closest('[data-field-type="repeater"], [data-field-type="tag-list"]');
+
+		let name, value;
+		if (collection) {
+			name = collection.dataset.field;
+			value = this.forms.getFieldValue(collection);
+		} else {
+			let field = e.target.closest('[data-field]');
+			name = field.dataset.field;
+			value = this.forms.getFieldValue(e.target);
+		}
+
 		item.dataset.itemId.split(',').forEach(itemId => {
 			this.updateItem(itemId, name, value);
 		});
diff --git a/assets/js/min/crud.min.js b/assets/js/min/crud.min.js
index f877f4f..a10477c 100644
--- a/assets/js/min/crud.min.js
+++ b/assets/js/min/crud.min.js
@@ -1 +1 @@
-(()=>{class e{constructor(){this.container=document.querySelector(".crud[data-content]:not([data-ignore])"),this.container&&(this.content=this.container.dataset.content,this.endpoint=this.container.dataset.endpoint??"content",this.singular=this.container.dataset.singular,this.plural=this.container.dataset.plural,this.queue=window.jvbQueue,this.a11y=window.jvbA11y,this.error=window.jvbError,this.populate=window.jvbPopulate,this.cache=new window.jvbCache(this.content),this.activeItem=null,this.isTimeline=!1,this.isPopulating=!1,this.changes=new Map,this.items=new Map,this.init())}init(){this.initElements(),this.initListeners(),this.defineTemplates();let e=this.initSettings();this.initStore(e),this.checkHideFilters(),this.initIntegrations(),this.initUploader(),this.initModals()}defineTemplates(){const e=window.jvbTemplates,t=this,i=(e,i,s)=>{e.dataset.itemId=s.id;let a=i.checkbox.closest(".preview");window.prefixInput(i.checkbox,`select-${s.id}`,a,!0),i.checkbox.value=s.id,i.checkbox.checked=t.selected.has(parseInt(s.id)),i.selectLabel&&(i.selectLabel.htmlFor=`select-${s.id}`),i.edit&&(i.edit.dataset.id=s.id),i.trash&&(i.trash.dataset.id=s.id)},s=function(e,t,i){if(i?.fields?.post_thumbnail){const e=i.images[i.fields.post_thumbnail]??{};t.img.src=e.medium??"",t.img.alt=e.alt??i.fields.post_title??""}};e.define("gridView",{refs:{img:"img",checkbox:".select-item",selectLabel:"label.select-item-label",edit:'[data-action="edit"]',trash:'[data-action="trash"]'},setup({el:e,refs:t,manyRefs:a,data:l}){i(e,t,l),s(0,t,l)}}),e.define("listView",{refs:{img:"img",checkbox:".select-item",selectLabel:"label.select-item-label",edit:'[data-action="edit"]',trash:'[data-action="trash"]'},manyRefs:{attrs:"[data-attr]",fields:"[data-field]"},setup({el:e,refs:t,manyRefs:a,data:l}){i(e,t,l),s(0,t,l),a?.attrs?.forEach((e=>{const t=l[e.dataset.attr];t&&""!==t?e.textContent=t:e.remove()})),a?.fields?.forEach((e=>{const t=l.fields?.[e.dataset.field];t&&""!==t?"DIV"===e.tagName?e.innerHTML=t:e.textContent=t:e.remove()}))}});let a={};this.isTimeline&&(a.sharedRow="tr.shared",a.point="tr.timeline-point"),e.define("tableView",{refs:{checkbox:".select-item",selectLabel:"label.select-item-label",...a},manyRefs:{inputs:"input,select,textarea",status:'input[name="post_status"]',selectors:'[data-type="selector"]',fields:"[data-field]"},setup({el:e,refs:s,manyRefs:a,data:l}){if(i(e,s,l),a?.inputs?.forEach((e=>{let t=e.closest("[data-field]");window.prefixInput(e,`${l.id}-`,t)})),a?.status?.forEach((e=>{e.value===l.status&&(e.checked=!0)})),t.isTimeline)s.sharedRow&&(s.sharedRow.querySelectorAll("input,select,textarea").forEach((e=>{let t=e.closest("[data-field]");window.prefixInput(e,`${l.id}-`,t)})),t.populate.populate(s.sharedRow,l),s.sharedRow.querySelectorAll('input[name="post_status"]').forEach((e=>{e.value===l.status&&(e.checked=!0)}))),s.point&&l.fields?.timeline&&(Object.entries(l.fields.timeline).forEach((([i,a],n)=>{const o=s.point.cloneNode(!0);o.dataset.index=`${n}`,o.dataset.itemId=a.id,o.querySelectorAll("input,select,textarea").forEach((e=>{let t=e.closest("[data-field]");window.prefixInput(e,`${a.id}-`,t)})),t.populate.populate(o,{fields:a,images:l.images,taxonomies:l.taxonomies});const d=l.images?.[a.post_thumbnail];d&&o.querySelector(".field.upload")?.setAttribute("title",d["image-title"]??""),e.insertBefore(o,s.point)})),s.point.remove());else if(void 0!==t.ui.table.form?.dataset.edit)a?.inputs?.forEach((e=>{let t=e.closest("[data-field]");window.prefixInput(e,`${l.id}-`,t)})),a?.status?.forEach((e=>{e.value===l.status&&(e.checked=!0)})),t.populate.populate(e,l);else{const e=Object.hasOwn(l,"fields")?l.fields:l;a?.fields?.forEach((t=>{if(Object.hasOwn(e,t.dataset.field)&&""!==e[t.dataset.field]){let i=e[t.dataset.field],s=e.children[0];s&&(s.textContent="date"===t.dataset.field?window.formatTimeAgo(i):i)}}))}a?.selectors?.forEach((e=>e.setAttribute("data-lazy","")))}}),e.define("emptyState"),e.define("bulkItem",{refs:{checkbox:"input",img:"img",label:"label"},setup({el:e,refs:t,manyRefs:i,data:s}){t.checkbox&&(t.checkbox.id=`bulk_${s.id}`,t.checkbox.value=s.id,t.checkbox.checked=!0,t.checkbox.name="selected[]");let a=s?.images[s?.fields?.post_thumnbail]??{};t.img&&Object.keys(a).length>0&&(t.img.src=a.medium??"",t.img.alt=a.alt??""),t.label&&(t.label.title=item.fields.post_title)}}),e.define("trashOptions"),e.define("notTrashOptions"),e.define("contentTable")}initElements(){this.allowedFilters=["status","orderby","order","search","date-filter","dateFrom","dateTo"],this.selectors={buttons:{create:".create-item",clearFilters:'[data-action="clear-filters"]'},views:{grid:'input[data-view="grid"]',list:'input[data-view="list"]',table:'input[data-view="table"]'},modals:{create:{modal:"dialog.create",form:"dialog.create form",h2:"dialog.create h2"},edit:{modal:"dialog.edit",form:"dialog.edit form",h2:"dialog.edit h2"},bulkEdit:{modal:"dialog.bulkEdit",selected:"dialog.bulkEdit .selected",h2:"dialog.bulkEdit h2 span",form:"dialog.bulkEdit form"},date:{modal:"dialog.date-range",start:"dialog.date-range .date-start",end:"dialog.date-range .date-end",month:"dialog.date-range .month-select"}},grid:`.${this.content}.item-grid`,table:{nav:"#vertical",form:"form.table",table:"form.table table",body:"form.table body",head:"form.table thead",foot:"form.table tfoot",selectedColumns:".all-filters .multi-select",columns:"thead th"},bulk:{action:".bulk-action-select",count:".bulk-controls .selected-count",control:".bulk-controls .bulk-actions",select:".bulk-controls select",selectAll:".select-all"},filters:{container:"details.all-filters",search:'.all-filters input[type="search"]',status:{all:'[name="status"]#all',publish:'[name="status"]#publish',draft:'[name="status"]#draft',trash:'[name="status"]#trash'},orderby:{date:'[name="orderby"]#date',alphabetical:'[name="orderby"]#alphabetical'},order:{asc:'[name="order"][value="asc"]',desc:'[name="order"][value="desc"]'},date:'[data-filter="date"]'},uploader:"details.uploader"},this.ui=window.uiFromSelectors(this.selectors);const e=document.querySelectorAll('[data-filter="taxonomies"]');e.length>0&&(this.ui.filters.taxonomies={},e.forEach((e=>{const t=e.dataset.taxonomy;this.ui.filters.taxonomies[t]=e,this.allowedFilters.push(`tax_${t}`)}))),this.isTimeline=!!document.querySelector("[data-timeline]")}initUploader(){this.ui.uploader&&(window.jvbUploads.scanFields(this.ui.uploader),window.jvbUploads.subscribe(((e,t)=>{if("sent-to-queue"===e&&t===this.ui.uploader.dataset.uploader&&window.debouncer.schedule("crud-complete",(()=>{this.store.clearCache()})),"sent-to-queue"===e&&t.field){const e=t.field.config.name,i=t.field.config.itemID;i&&e&&this.changes.has(i)&&delete this.changes.get(i)[e]}})))}initModals(){this.modals={};for(let[e,t]of Object.entries(this.ui.modals))t.modal&&(this.modals[e]=new window.jvbModal(t.modal),this.modals[e].subscribe(((t,i)=>{if("modal-close"===t){const t=this.ui.modals[e].form.dataset.formId;t&&this.forms.clearForm(t),this.resetForm(this.ui.modals[e].form),"date"===e&&this.handleCustomDateSelection(),["edit","bulkEdit","create"].includes(e)&&window.debouncer.timeouts.has(`save-${this.content}`)&&this.scheduleSave(0)}})))}initStore(e){let t={...this.defaults,...e};const i=window.jvbStore.register(this.content,[{storeName:this.content,keyPath:"id",endpoint:this.endpoint??"content",headers:{"X-Action-Nonce":window.auth.getNonce("dash")},indexes:[{name:"id",keyPath:"id"},{name:"status",keyPath:"status"},{name:"date",keyPath:"date"},{name:"modified",keyPath:"modified"},{name:"title",keyPath:"title"}],filters:t,ignore:["content","user"],TTL:36e5,showLoading:!0},{storeName:"changes",keyPath:"id"}]);this.changesStore=i.changes,this.store=i[this.content],this.store.subscribe(((e,t)=>{if("data-loaded"===e)this.render(),this.selectionHandler.collectItems()})),this.changesStore.subscribe(((e,t)=>{if("data-ready"===e){let e=this.changesStore.getAll();e.length>0&&(e.forEach((e=>{this.changes.set(e.id,e)})),this.savePosts("",!1).then((()=>{})))}}))}initIntegrations(){this.selected=new Set,this.selectionHandler=new window.jvbHandleSelection(this.container,{selectAll:{checkbox:"#select-all",label:".bulk-select label",span:".bulk-select label span"},wrapper:{wrapper:".wrap"},item:{idAttribute:"itemId"}}),this.selectionHandler.subscribe(((e,t)=>{this.selected=new Set([...t.selectedItems].map((e=>parseInt(e)))),this.ui.bulk.control.hidden=0===this.selected.size,this.ui.bulk.count.hidden=0===this.selected.size,this.ui.bulk.count.textContent=`${this.selected.size} ${this.plural} selected`})),this.forms=window.jvbForm,window.jvbUploads&&window.jvbUploads.subscribe(((e,t)=>{"groups_uploaded"===e&&t.content===this.content&&this.handleGroupsUploaded(t)})),this.queue.subscribe(((e,t)=>{if(["image_upload","video_upload","document_upload"].includes(t.type)&&"operation-status"===e&&"completed"===t.status&&this.store.clearCache(),"operation-status"===e&&"completed"===t.status&&"uploads/groups"===t.endpoint&&(t.result&&t.result.group_mappings&&this.handleGroupMappings(t.result.group_mappings),this.store.clearCache()),"operation-status"===e&&"completed"===t.status&&"content_update"===t.type){if(this.store.clearCache(),!t.result||!t.result.posts)return void console.warn("Content update completed but no result.posts",t);const e=Object.keys(t.result.posts);if(0===e.length)return;this.changesStore.deleteMany(e),e.forEach((e=>this.changes.delete(e)))}}))}initSettings(){this.defaults={content:this.content,user:window.auth.getUser(),page:1,status:"all",orderby:"date",order:"desc",search:""};let e={},t=this.container.dataset.view??"grid";this.view=this.cache.get("view")??t,this.view!==t&&(this.ui.views[this.view].checked=!0),this.status=this.cache.get("status")??this.defaults.status,this.status!==this.defaults.status&&(this.ui.filters.status[this.status].checked=!0,e.status=this.status),this.orderby=this.cache.get("orderby")??this.defaults.orderby,this.orderby!==this.defaults.orderby&&(this.ui.filters.orderby[this.orderby].checked=!0,e.orderBy=this.orderby),this.order=this.cache.get("order")??this.defaults.order,this.order!==this.defaults.order&&(this.ui.filters.order[this.order].checked=!0,e.order=this.order),this.ui.filters.taxonomies&&Object.entries(this.ui.filters.taxonomies).forEach((([t,i])=>{const s=`tax_${t}`,a=this.cache.get(s);a&&(i.value=a,e[s]=a)}));let i=this.cache.get("tabNav")??"horizontal";this.ui.table.nav&&"vertical"===i&&(this.ui.table.nav.checked=!0);let s={showFilters:{element:this.ui.filters.container,default:"closed"},showUploader:{element:this.ui.uploader,default:"open"}};for(let[e,t]of Object.entries(s))if(t.element){let i=this.cache.get(e)??t.default;t.element.open="open"===i,t.element.addEventListener("toggle",(()=>{this.cache.set(e,t.element.open?"open":"closed")}))}return e}initListeners(){this.changeHandler=this.handleChange.bind(this),this.clickHandler=this.handleClick.bind(this),this.inputHandler=this.handleInput.bind(this),this.submitHandler=this.handleModalSubmit.bind(this),document.addEventListener("change",this.changeHandler),document.addEventListener("click",this.clickHandler),this.ui.filters.search&&this.ui.filters.search.addEventListener("input",this.inputHandler);for(let[e,t]of Object.entries(this.ui.modals))t.form&&t.form.addEventListener("submit",this.submitHandler)}handleModalSubmit(e){e.preventDefault();const t=e.target.closest("dialog");if(!t)return;if(t.classList.contains("create"))return void this.handleCreateSubmit(t);this.plural;this.scheduleSave(0)}async handleCreateSubmit(e){e.dataset.itemId;this.changes.size>0&&(this.cancelBackup(),await this.handleBackup());const t=await this.changesStore.getAll();if(0===t.length)return;let i={};t.forEach((e=>{const{id:t,...s}=e;i[t]=s}));let s=this.queue.addToQueue({endpoint:this.endpoint,headers:{"X-Action-Nonce":window.auth.getNonce("dash")},data:{posts:i},popup:`Creating your new ${this.singular}`,title:`Creating your new ${this.singular}`});if(!s)return;const a=e.querySelectorAll("[data-upload-field]");for(const e of a){const t=e.dataset.uploader;if(!t)continue;0!==window.jvbUploads.stores.uploads.filterByIndex({field:t}).length&&await window.jvbUploads.queueUploads("uploads",t,s)}}handleChange(e){const t=e.target.closest("[data-item-id]"),i=e.target.matches("[data-filter]"),s=e.target.matches(".bulk-action-select"),a=e.target.matches("[data-view]");if(t||i||s||a)if(this.isPopulating||!t||e.target.closest("[data-ignore], .select-item")){if(a)return this.items.clear(),void this.handleViewChange(e.target);if(s)this.handleBulkAction(e.target);else if(i)this.handleFilterChange(e.target);else if("table"===this.view){if(e.target.matches("details.multi-select"))return void this.toggleColumn(e.target.id,e.target.checked);e.target.matches(this.selectors.table.nav)&&(this.tabNav=e.target.checked,this.cache.set("tabNav",e.target.checked?"vertical":"horizontal"))}}else this.handleItemUpdate(e)}handleBulkAction(e){if(e.value.startsWith("tax-")){const t=e.options[e.selectedIndex],i=t.dataset.taxonomy,s=t.dataset.single,a=t.dataset.plural;return window.jvbSelector.openEmpty(i,s,a,(e=>this.handleBulkTaxonomy(e))),void(e.value="")}switch(e.value){case"edit":this.openBulkEditModal();break;case"publish":case"trash":case"delete":this.setBulkStatus(e.value);break;case"draft":case"restore":this.setBulkStatus("draft")}}handleBulkTaxonomy(e){e.termIds.length&&this.selected.size&&(this.selected.forEach((t=>{const i=this.store.get(t);if(!i)return;const s=(i.taxonomies?.[e.taxonomy]||[]).map((e=>e.id)),a=[...new Set([...s,...e.termIds])];this.updateItem(t,e.taxonomy,a)})),this.savePosts(`Adding ${e.terms.length} ${e.taxonomy} to ${this.selected.size} ${this.plural}...`).then((()=>{})),this.selectionHandler.clearSelection())}handleItemUpdate(e){let t=window.targetCheck(e,"[data-item-id]");if(!t)return;let i=e.target.closest('[data-field-type="repeater"],[data-field-type="tag-list"],[data-field]').dataset.field,s=this.forms.getFieldValue(e.target);t.dataset.itemId.split(",").forEach((e=>{this.updateItem(e,i,s)}))}updateItem(e,t,i){if(this.isPopulating)return;const s=this.store.get(e);if(s){const a=s.fields?.[t]??s[t];if(null===window.getDifferences.map(a,i)){if(this.changes.has(e)){delete this.changes.get(e)[t];0===Object.keys(this.changes.get(e)).filter((e=>"id"!==e&&"content"!==e)).length&&(this.changes.delete(e),this.changesStore.delete(e))}return}}this.changes.has(e)||this.changes.set(e,{id:e,content:this.content}),this.changes.get(e)[t]=i,this.scheduleBackup(),"number"!=typeof e&&String(e).includes("group")||this.scheduleSave()}scheduleBackup(){window.debouncer.schedule(`changes-${this.content}`,(async()=>{this.changes.size>0&&await this.handleBackup()}),2e3)}cancelBackup(){window.debouncer.cancel(`changes-${this.content}`)}async handleBackup(){const e=Array.from(this.changes.values());this.changes.clear();const t=e.map((e=>e.id)),i=await Promise.all(t.map((e=>this.changesStore.get(e)))),s=e.map(((e,t)=>i[t]?window.deepMerge(i[t],e):e));await this.changesStore.saveMany(s)}scheduleSave(e=1e4){window.debouncer.schedule(`save-${this.content}`,(async()=>{this.changes.size>0&&(this.cancelBackup(),await this.handleBackup()),await this.savePosts("",!1)}),e)}handleFilterChange(e){let t=e.dataset.filter;return"date"===t&&"custom"===e.value?(e.value="",void this.modals.date.handleOpen()):"date"===t&&""!==e.value?(this.setFilter("date-filter",e.value),this.deleteFilter("dateFrom"),this.deleteFilter("dateTo"),void this.checkHideFilters()):("taxonomies"===t&&(t=`tax_${e.dataset.taxonomy}`),void this.setFilter(t,e.value))}checkHideFilters(){const e=this.store.filters,t=Object.entries(e).some((([e,t])=>!["content","user","page"].includes(e)&&(this.defaults[e]!==t&&""!==t&&null!==t)));this.ui.buttons.clearFilters.hidden=!t}clearAllFilters(){let e=this.store.filters;this.store.clearFilters();for(let[t,i]of Object.entries(e))this.cache.remove(t),this.deleteFilter(t,i);this.a11y.announce("All filters cleared")}handleCustomDateSelection(){if(this.ui.modals.date.month&&this.ui.modals.date.month.value){const[e,t]=this.ui.modals.date.month.value.split("-"),i=`${e}-${t}-01`,s=new Date(e,parseInt(t),0).getDate(),a=`${e}-${t}-${String(s).padStart(2,"0")}`;this.setFilter("dateFrom",i),this.setFilter("dateTo",a),this.deleteFilter("date-filter"),this.ui.modals.date.month.value=""}else this.ui.modals.date.start&&this.ui.modals.date.start.value&&this.ui.modals.date.end&&this.ui.modals.date.end.value&&(this.setFilter("dateFrom",this.ui.modals.date.start.value),this.setFilter("dateTo",this.ui.modals.date.end.value),this.deleteFilter("date-filter"),this.ui.modals.date.start.value="",this.ui.modals.date.end.value="");this.checkHideFilters()}handleViewChange(e){this.view=e.dataset.view,this.cache.set("view",this.view),this.render()}handleClick(e){if(e.target.matches(".clear-search"))return void this.deleteFilter("search","");const t=e.target.closest("[data-action]");return t?(e.preventDefault(),void this.handleActionButton(t)):e.target.matches(".apply-date-filter")?(this.handleCustomDateSelection(),void this.modals.date.handleClose()):void(e.target.matches(this.selectors.buttons.create)&&this.openCreateModal())}openCreateModal(){this.forms.registerForm(this.ui.modals.create.form,{cache:!1}),this.ui.modals.create.modal.dataset.itemId=window.generateID("new"),this.modals.create.handleOpen()}handleActionButton(e){const t=e.dataset.id;switch(e.dataset.action){case"edit":this.openEditModal(t);break;case"delete":confirm("Delete this item? This cannot be undone")&&(this.updateItem(t,"post_status","delete"),window.fade(e.closest(".item"),!1),this.savePosts(`Permanently deleting ${this.singular}...`).then((()=>{})),this.store.delete(t));break;case"trash":"trash"===this.status?confirm("Delete this item? This cannot be undone")&&(this.updateItem(t,"post_status","delete"),window.fade(e.closest(".item"),!1),this.savePosts(`Permanently deleting ${this.singular}...`).then((()=>{})),this.store.delete(t)):(this.updateItem(t,"post_status","trash"),window.fade(e.closest(".item"),!1),this.savePosts(`Sending ${this.singular} to trash...`).then((()=>{})));break;case"bulk-edit":this.selected.size>0&&this.openBulkEditModal();break;case"bulk-delete":this.handleBulkDelete();break;case"refresh":this.store.clearCache(),this.store.fetch();break;case"clear-filters":this.clearAllFilters()}}handleBulkDelete(){let e="trash"===this.status;if(this.selected.size>0&&confirm(`${e?"Permanently delete":"Send"} ${this.selected.size} ${1===this.selected.size?this.singular:this.plural}${e?"":"to trash"}?`)){this.selected.forEach((t=>{this.store.delete(t),this.updateItem(t,"post_status",e?"delete":"trash")}));let t=e?`Permanently deleting ${this.selected.size} ${1===this.selected.size?this.singular:this.plural}`:`Sending ${this.selected.size} ${1===this.selected.size?this.singular:this.plural} to trash`;this.savePosts(t).then((()=>{})),this.selectionHandler.clearSelection()}}handleInput(e){e.preventDefault(),e.stopPropagation();let t=e.target.value.trim(),i=`${this.content}-search`;0!==t.length?window.debouncer.schedule(i,(()=>{this.a11y.announce(`Searching for "${t}"...`),this.store.setFilters({search:t,page:1})}),300):this.deleteFilter("search","")}handleKeys(e){if(this.tabNav&&"Tab"===e.key){e.preventDefault();const t=e.target.closest("[data-field]"),i=e.target.closest("tr");if(!t||!i)return;const s=t.dataset.field,a=e.shiftKey;let l=this.findNextEditableRow(i,a);l||(l=this.wrapToRow(i,a)),l&&this.focusFieldInRow(l,s,a)}}findNextEditableRow(e,t=!1){let i=t?e.previousElementSibling:e.nextElementSibling;for(;i&&!this.isEditableRow(i);)i=t?i.previousElementSibling:i.nextElementSibling;return i}wrapToRow(e,t=!1){if(this.isTimeline){const i=e.closest("tbody");if(!i)return null;const s=Array.from(i.querySelectorAll("tr")).filter((e=>this.isEditableRow(e)));return t?s[s.length-1]:s[0]}{if(!this.ui.table.body)return null;const e=Array.from(this.ui.table.body.querySelectorAll("tr")).filter((e=>this.isEditableRow(e)));return t?e[e.length-1]:e[0]}}isEditableRow(e){return!e.closest("thead")&&!e.closest("tfoot")&&(this.isTimeline?e.classList.contains("shared")||e.classList.contains("timeline-point"):!!e.dataset.itemId)}focusFieldInRow(e,t,i=!1){const s=e.querySelector(`[data-field="${t}"]`);if(!s)return;const a=this.findFocusableInput(s);if(a){a.focus(),a.select&&"text"===a.type&&a.select();const e=i?"next":"previous";this.a11y?.announce(`Moved to ${t} in ${e} row`)}}findFocusableInput(e){const t=['input:not([type="hidden"]):not([disabled])',"textarea:not([disabled])","select:not([disabled])","button:not([disabled])"];for(const i of t){const t=e.querySelector(i);if(t)return t}return null}openEditModal(e){let t=this.store.get(parseInt(e));t&&(this.activeItem=t.id,this.ui.modals.edit.modal.dataset.itemId=e,this.ui.modals.edit.modal.dataset.content=this.content,this.ui.modals.edit.h2.textContent=`Editing ${""===t.fields.post_title?this.singular:t.fields.post_title}`,this.ui.modals.edit.form.dataset.formId=`edit-${e}`,this.forms.registerForm(this.ui.modals.edit.form,{cache:!1,autoUpload:!0}),this.isPopulating=!0,this.populate.populate(this.ui.modals.edit.form,t),requestAnimationFrame((()=>{requestAnimationFrame((()=>{this.isPopulating=!1}))})),this.modals.edit.handleOpen())}openBulkEditModal(){window.removeChildren(this.ui.modals.bulkEdit.selected),this.ui.modals.edit.form.reset(),window.chunkIt(this.selected,(t=>{let i=this.store.get(parseInt(t));if(i)return e.push(i.id),window.jvbTemplates.create("bulkItem",i)}),(e=>this.ui.modals.bulkEdit.selected.append(e))).then((()=>{}));let e=Array.from(this.selected).map((e=>this.store.get(parseInt(e)))).filter(Boolean);this.ui.modals.bulkEdit.modal.dataset.itemId=e.join(","),this.ui.modals.bulkEdit.h2&&(this.ui.modals.bulkEdit.h2.textContent=this.selected.size),this.modals.bulkEdit.handleOpen(),this.forms.registerForm(this.ui.modals.bulkEdit.form,{cache:!1}),this.isPopulating=!0,this.populate.populate(this.ui.modals.edit.form,item),requestAnimationFrame((()=>{requestAnimationFrame((()=>{this.isPopulating=!1}))}))}async savePosts(e="",t=!1){this.changes.size>0&&(this.cancelBackup(),await this.handleBackup());let i=await this.changesStore.getAll();if(0===i.length)return;if(i=this.validateChanges(i),0===i.length)return;""===e&&(e=`Saving ${i.length} ${1===i.length?this.singular:this.plural}`);let s={},a=[];i.forEach((e=>{let t=e.id;const{id:i,...l}=e;s[t]=l,e.post_status&&this.shouldRemoveItemUI(e.post_status)&&a.push(t)})),a.length>0&&this.removeItems(a);let l={endpoint:this.endpoint,headers:{"X-Action-Nonce":window.auth.getNonce("dash")},data:{posts:s},delay:t,popup:"Saving changes",title:e};this.queue.addToQueue(l)}validateChanges(e){return e.reduce(((e,t)=>{const{id:i,content:s,...a}=t,l=this.store.get(i);if(!l)return e.push(t),e;const n={id:i,content:s};let o=!1;for(const[e,t]of Object.entries(a)){const i=l.fields?.[e]??l[e];null!==window.getDifferences.map(i,t)&&(n[e]=t,o=!0)}return o?e.push(n):(this.changes.delete(i),this.changesStore.delete(i)),e}),[])}setBulkStatus(e){if(!["publish","draft","trash","delete"].includes(e))return;let t,i=[];if(this.selected.forEach((t=>{i.push(t),this.updateItem(t,"post_status",e)})),"delete"===e)t="Deleting";else t=window.uppercaseFirst(e)+"ing";this.shouldRemoveItemUI(e)&&this.removeItems(i),this.selectionHandler.clearSelection(),this.savePosts(`${t} ${i.length} ${1===i.length?this.singular:this.plural}...`).then((()=>{}))}render(){const e=this.store.getFiltered();if(0!==e.length){switch(this.view){case"grid":this.renderGrid(e);break;case"table":this.renderTable(e).then((()=>{}));break;case"list":this.renderList(e)}this.updateUI()}else this.renderEmpty()}updateUI(){if(this.ui.bulk.action){let e=!1,t=this.ui.bulk.action.querySelector('[value="edit"]'),i=this.status;"trash"===i&&t?(window.removeChildren(this.ui.bulk.action),e=window.jvbTemplates.create("trashOptions")):"trash"===i||t||(window.removeChildren(this.ui.bulk.action),e=window.jvbTemplates.create("notTrashOptions")),e&&e.querySelectorAll("option").forEach(((e,t)=>{0===t&&(e.checked=!0),this.ui.bulk.action.append(e)})),this.ui.bulk.action.value=""}this.selected.size>0&&this.selectionHandler.updateSelectionUI()}renderEmpty(){this.toggleTable(!1),window.removeChildren(this.ui.grid);const e=window.jvbTemplates.create("emptyState");e&&(this.ui.grid.append(e),this.a11y.announceItems(0,!1,!1))}toggleTable(e=!0){if(this.ui.table.selectedColumns&&(this.ui.table.selectedColumns.hidden=!e),e&&!this.ui.table.form){let e=window.jvbTemplates.create("contentTable");this.container.append(e),this.ui.table=window.uiFromSelectors(this.selectors.table),this.ui.table.columns=this.container.querySelectorAll(this.selectors.table.columns)}this.ui.table.form&&(this.ui.table.form.hidden=!e,e||this.forms.clearForm(this.ui.table.form.dataset.formId),this.ui.table.body&&window.removeChildren(this.ui.table.body)),this.keyHandler=this.handleKeys.bind(this),e?document.addEventListener("keydown",this.keyHandler):document.removeEventListener("keydown",this.keyHandler)}renderGrid(e){window.removeChildren(this.ui.grid),this.toggleTable(!1),this.ui.grid.classList.remove("list-view"),this.ui.grid.classList.add("grid-view"),window.chunkIt(e,(e=>this.renderGridItem(e)),(e=>this.ui.grid.append(e))).then((()=>{}))}renderList(e){window.removeChildren(this.ui.grid),this.toggleTable(!1),this.ui.grid.classList.remove("grid-view"),this.ui.grid.classList.add("list-view"),window.chunkIt(e,(e=>this.renderListItem(e)),(e=>this.ui.grid.append(e))).then((()=>{}))}async renderTable(e){this.toggleTable(),window.removeChildren(this.ui.grid),await window.chunkIt(e,(e=>this.renderTableItem(e)),(e=>{this.ui.table.body?this.ui.table.body.append(e):this.ui.table.table.insertBefore(e,this.ui.table.foot)}),5),requestAnimationFrame((()=>{window.jvbSelector?.scanExistingFields(this.ui.table.table)}))}renderGridItem(e){let t=window.jvbTemplates.create("gridView",e);return this.items.set(e.id,t),t}renderListItem(e){let t=window.jvbTemplates.create("listView",e);return this.items.set(e.id,t),t}renderTableItem(e){let t=window.jvbTemplates.create("tableView",e);return this.items.set(e.id,t),t}toggleColumn(e,t){this.ui.table.table.querySelectorAll(`.${e}`).forEach((e=>{e.hidden=!t}))}handleGroupsUploaded(e){const{posts:t,fieldId:i}=e;let s=window.jvbUploads,a=(s.fields.get(i),[]);t.forEach((e=>{const t={id:e.groupId,title:e.fields.post_title||`New ${this.singular}`,status:"draft",date:(new Date).toISOString(),modified:(new Date).toISOString(),thumbnail:null,icon:this.content,taxonomies:{},fields:e.fields,images:{}};e.images.forEach(((e,i)=>{let a=e.upload_id;0===i&&(t.fields.post_thumbnail=e);let l=s.stores.uploads.get(a);l&&(t.images[a]={"image-alt-text":"","image-caption":"","image-title":l.fields.originalName,medium:s.createPreviewUrl(s.formatFile(l))})})),a.push(t)})),this.store.saveMany(a).then((()=>this.render())),this.a11y.announce(`${t.length} ${1===t.length?this.singular:this.plural} created. Waiting for server confirmation...`)}handleGroupMappings(e){for(const[t,i]of Object.entries(e)){let e={};this.changes.has(t)&&(e=this.changes.get(t),this.changes.delete(t));let s=this.changesStore.get(t)??{};(e.size>0||s.size>0)&&(e=window.deepMerge(s,e),this.changes.set(i,e),this.scheduleBackup())}}shouldRemoveItemUI(e){return"all"===this.status&&!["publish","draft"].includes(e)||e!==this.store.filters.status}removeItems(e){e.forEach((e=>{if(this.items.has(e)){let t=this.items.get(e);t&&window.fade(t,!1)}}))}setFilters(e){for(let[t,i]of Object.entries(e)){if(!this.allowedFilters.includes(t)){delete e[t];continue}this.cache.set(t,i);let s=this.findFilterEl(t);this.setElValue(s,i)}this.store.setFilters(e)}setFilter(e,t){if(!this.allowedFilters.includes(e))return;this.cache.set(e,t),"status"===e&&(this.status=t),"orderby"===e&&(this.orderby=t),"order"===e&&(this.order=t);let i=this.findFilterEl(e,t);this.setElValue(i,t),this.store.setFilter(e,t)}deleteFilter(e,t){if(!this.allowedFilters.includes(e))return;if(Object.hasOwn(this.defaults,e))return void this.setFilter(e,this.defaults[e]);let i=this.findFilterEl(e,t);this.setElValue(i,!1),this.cache.remove(e),this.setFilter(e,"")}setElValue(e,t){if(e){if(!t)return["SELECT","TEXTAREA"].includes(e.tagName)&&(e.value=""),["text","search"].includes(e.type)&&(e.value=""),void("radio"===e.type&&(e.checked=!1));["SELECT","TEXTAREA"].includes(e.tagName)&&(e.value=t),["text","search"].includes(e.type)&&(e.value=t),"radio"===e.type&&(e.checked=!0)}}findFilterEl(e,t){if(["date-filter","dateFrom","dateTo"].includes(e)){switch(e){case"date-filter":e="month";break;case"dateFrom":e="start";break;case"dateTo":e="end"}return this.ui.modals.date[e]}if(e.includes("tax_")){const t=e.replace("tax_",""),i=this.ui.filters.taxonomies?.[t];return i||(console.warn("Taxonomy filter element not found:",t),null)}if(!Object.hasOwn(this.ui.filters,e))return console.warn("Filter el not found: ",e),!1;let i=this.ui.filters[e];if("object"==typeof i){if(!Object.hasOwn(this.ui.filters[e],t))return!1;i=this.ui.filters[e][t]}return i}resetForm(e){e.querySelectorAll('input[type="hidden"], input[type="text"], input[type="number"], input[type="email"], input[type="url"], textarea').forEach((e=>{e.value=""})),e.querySelectorAll('input[type="checkbox"], input[type="radio"]').forEach((e=>{e.checked=!1})),e.querySelectorAll("select").forEach((e=>{e.selectedIndex=0})),e.querySelectorAll(".selected-items").forEach((e=>{window.removeChildren(e)})),e.querySelectorAll(".item-grid.preview").forEach((e=>{window.removeChildren(e)}))}destroy(){window.debouncer.cancel(`changes-${this.content}`),this.changes.size>0&&(this.changesStore.saveMany(this.changes).then((()=>{})),this.changes.clear()),this.timelineSortables&&(this.timelineSortables.forEach((e=>e.destroy())),this.timelineSortables=[]);for(let[e,t]of Object.entries(this.ui.modals))t.form&&t.form.removeEventListener("submit",this.submitHandler);document.removeEventListener("click",this.clickHandler),document.removeEventListener("change",this.changeHandler),this.ui.filters.search&&this.ui.filters.search.removeEventListener("input",this.handleInput)}}document.addEventListener("DOMContentLoaded",(async function(){window.auth.subscribe((t=>{if("auth-loaded"===t){let t=document.querySelector("[data-content]");t&&!Object.hasOwn(t.dataset,"ignore")&&(window.crudManager=new e({content:t.dataset.content}))}}))}))})();
\ No newline at end of file
+(()=>{class e{constructor(){this.container=document.querySelector(".crud[data-content]:not([data-ignore])"),this.container&&(this.content=this.container.dataset.content,this.endpoint=this.container.dataset.endpoint??"content",this.singular=this.container.dataset.singular,this.plural=this.container.dataset.plural,this.queue=window.jvbQueue,this.a11y=window.jvbA11y,this.error=window.jvbError,this.populate=window.jvbPopulate,this.cache=new window.jvbCache(this.content),this.activeItem=null,this.isTimeline=!1,this.isPopulating=!1,this.changes=new Map,this.items=new Map,this.init())}init(){this.initElements(),this.initListeners(),this.defineTemplates();let e=this.initSettings();this.initStore(e),this.checkHideFilters(),this.initIntegrations(),this.initUploader(),this.initModals()}defineTemplates(){const e=window.jvbTemplates,t=this,i=(e,i,s)=>{e.dataset.itemId=s.id;let a=i.checkbox.closest(".preview");window.prefixInput(i.checkbox,`select-${s.id}`,a,!0),i.checkbox.value=s.id,i.checkbox.checked=t.selected.has(parseInt(s.id)),i.selectLabel&&(i.selectLabel.htmlFor=`select-${s.id}`),i.edit&&(i.edit.dataset.id=s.id),i.trash&&(i.trash.dataset.id=s.id)},s=function(e,t,i){if(i?.fields?.post_thumbnail){const e=i.images[i.fields.post_thumbnail]??{};t.img.src=e.medium??"",t.img.alt=e.alt??i.fields.post_title??""}};e.define("gridView",{refs:{img:"img",checkbox:".select-item",selectLabel:"label.select-item-label",edit:'[data-action="edit"]',trash:'[data-action="trash"]'},setup({el:e,refs:t,manyRefs:a,data:l}){i(e,t,l),s(0,t,l)}}),e.define("listView",{refs:{img:"img",checkbox:".select-item",selectLabel:"label.select-item-label",edit:'[data-action="edit"]',trash:'[data-action="trash"]'},manyRefs:{attrs:"[data-attr]",fields:"[data-field]"},setup({el:e,refs:t,manyRefs:a,data:l}){i(e,t,l),s(0,t,l),a?.attrs?.forEach((e=>{const t=l[e.dataset.attr];t&&""!==t?e.textContent=t:e.remove()})),a?.fields?.forEach((e=>{const t=l.fields?.[e.dataset.field];t&&""!==t?"DIV"===e.tagName?e.innerHTML=t:e.textContent=t:e.remove()}))}});let a={};this.isTimeline&&(a.sharedRow="tr.shared",a.point="tr.timeline-point"),e.define("tableView",{refs:{checkbox:".select-item",selectLabel:"label.select-item-label",...a},manyRefs:{inputs:"input,select,textarea",status:'input[name="post_status"]',selectors:'[data-type="selector"]',fields:"[data-field]"},setup({el:e,refs:s,manyRefs:a,data:l}){if(i(e,s,l),a?.inputs?.forEach((e=>{let t=e.closest("[data-field]");window.prefixInput(e,`${l.id}-`,t)})),a?.status?.forEach((e=>{e.value===l.status&&(e.checked=!0)})),t.isTimeline)s.sharedRow&&(s.sharedRow.querySelectorAll("input,select,textarea").forEach((e=>{let t=e.closest("[data-field]");window.prefixInput(e,`${l.id}-`,t)})),t.populate.populate(s.sharedRow,l),s.sharedRow.querySelectorAll('input[name="post_status"]').forEach((e=>{e.value===l.status&&(e.checked=!0)}))),s.point&&l.fields?.timeline&&(Object.entries(l.fields.timeline).forEach((([i,a],n)=>{const o=s.point.cloneNode(!0);o.dataset.index=`${n}`,o.dataset.itemId=a.id,o.querySelectorAll("input,select,textarea").forEach((e=>{let t=e.closest("[data-field]");window.prefixInput(e,`${a.id}-`,t)})),t.populate.populate(o,{fields:a,images:l.images,taxonomies:l.taxonomies});const d=l.images?.[a.post_thumbnail];d&&o.querySelector(".field.upload")?.setAttribute("title",d["image-title"]??""),e.insertBefore(o,s.point)})),s.point.remove());else if(void 0!==t.ui.table.form?.dataset.edit)a?.inputs?.forEach((e=>{let t=e.closest("[data-field]");window.prefixInput(e,`${l.id}-`,t)})),a?.status?.forEach((e=>{e.value===l.status&&(e.checked=!0)})),t.populate.populate(e,l);else{const e=Object.hasOwn(l,"fields")?l.fields:l;a?.fields?.forEach((t=>{if(Object.hasOwn(e,t.dataset.field)&&""!==e[t.dataset.field]){let i=e[t.dataset.field],s=e.children[0];s&&(s.textContent="date"===t.dataset.field?window.formatTimeAgo(i):i)}}))}a?.selectors?.forEach((e=>e.setAttribute("data-lazy","")))}}),e.define("emptyState"),e.define("bulkItem",{refs:{checkbox:"input",img:"img",label:"label"},setup({el:e,refs:t,manyRefs:i,data:s}){t.checkbox&&(t.checkbox.id=`bulk_${s.id}`,t.checkbox.value=s.id,t.checkbox.checked=!0,t.checkbox.name="selected[]");let a=s?.images[s?.fields?.post_thumnbail]??{};t.img&&Object.keys(a).length>0&&(t.img.src=a.medium??"",t.img.alt=a.alt??""),t.label&&(t.label.title=item.fields.post_title)}}),e.define("trashOptions"),e.define("notTrashOptions"),e.define("contentTable")}initElements(){this.allowedFilters=["status","orderby","order","search","date-filter","dateFrom","dateTo"],this.selectors={buttons:{create:".create-item",clearFilters:'[data-action="clear-filters"]'},views:{grid:'input[data-view="grid"]',list:'input[data-view="list"]',table:'input[data-view="table"]'},modals:{create:{modal:"dialog.create",form:"dialog.create form",h2:"dialog.create h2"},edit:{modal:"dialog.edit",form:"dialog.edit form",h2:"dialog.edit h2"},bulkEdit:{modal:"dialog.bulkEdit",selected:"dialog.bulkEdit .selected",h2:"dialog.bulkEdit h2 span",form:"dialog.bulkEdit form"},date:{modal:"dialog.date-range",start:"dialog.date-range .date-start",end:"dialog.date-range .date-end",month:"dialog.date-range .month-select"}},grid:`.${this.content}.item-grid`,table:{nav:"#vertical",form:"form.table",table:"form.table table",body:"form.table body",head:"form.table thead",foot:"form.table tfoot",selectedColumns:".all-filters .multi-select",columns:"thead th"},bulk:{action:".bulk-action-select",count:".bulk-controls .selected-count",control:".bulk-controls .bulk-actions",select:".bulk-controls select",selectAll:".select-all"},filters:{container:"details.all-filters",search:'.all-filters input[type="search"]',status:{all:'[name="status"]#all',publish:'[name="status"]#publish',draft:'[name="status"]#draft',trash:'[name="status"]#trash'},orderby:{date:'[name="orderby"]#date',alphabetical:'[name="orderby"]#alphabetical'},order:{asc:'[name="order"][value="asc"]',desc:'[name="order"][value="desc"]'},date:'[data-filter="date"]'},uploader:"details.uploader"},this.ui=window.uiFromSelectors(this.selectors);const e=document.querySelectorAll('[data-filter="taxonomies"]');e.length>0&&(this.ui.filters.taxonomies={},e.forEach((e=>{const t=e.dataset.taxonomy;this.ui.filters.taxonomies[t]=e,this.allowedFilters.push(`tax_${t}`)}))),this.isTimeline=!!document.querySelector("[data-timeline]")}initUploader(){this.ui.uploader&&(window.jvbUploads.scanFields(this.ui.uploader),window.jvbUploads.subscribe(((e,t)=>{if("sent-to-queue"===e&&t===this.ui.uploader.dataset.uploader&&window.debouncer.schedule("crud-complete",(()=>{this.store.clearCache()})),"sent-to-queue"===e&&t.field){const e=t.field.config.name,i=t.field.config.itemID;i&&e&&this.changes.has(i)&&delete this.changes.get(i)[e]}})))}initModals(){this.modals={};for(let[e,t]of Object.entries(this.ui.modals))t.modal&&(this.modals[e]=new window.jvbModal(t.modal),this.modals[e].subscribe(((t,i)=>{if("modal-close"===t){const t=this.ui.modals[e].form.dataset.formId;t&&this.forms.clearForm(t),this.resetForm(this.ui.modals[e].form),"date"===e&&this.handleCustomDateSelection(),["edit","bulkEdit","create"].includes(e)&&window.debouncer.timeouts.has(`save-${this.content}`)&&this.scheduleSave(0)}})))}initStore(e){let t={...this.defaults,...e};const i=window.jvbStore.register(this.content,[{storeName:this.content,keyPath:"id",endpoint:this.endpoint??"content",headers:{"X-Action-Nonce":window.auth.getNonce("dash")},indexes:[{name:"id",keyPath:"id"},{name:"status",keyPath:"status"},{name:"date",keyPath:"date"},{name:"modified",keyPath:"modified"},{name:"title",keyPath:"title"}],filters:t,ignore:["content","user"],TTL:36e5,showLoading:!0},{storeName:"changes",keyPath:"id"}]);this.changesStore=i.changes,this.store=i[this.content],this.store.subscribe(((e,t)=>{if("data-loaded"===e)this.render(),this.selectionHandler.collectItems()})),this.changesStore.subscribe(((e,t)=>{if("data-ready"===e){let e=this.changesStore.getAll();e.length>0&&(e.forEach((e=>{this.changes.set(e.id,e)})),this.savePosts("",!1).then((()=>{})))}}))}initIntegrations(){this.selected=new Set,this.selectionHandler=new window.jvbHandleSelection(this.container,{selectAll:{checkbox:"#select-all",label:".bulk-select label",span:".bulk-select label span"},wrapper:{wrapper:".wrap"},item:{idAttribute:"itemId"}}),this.selectionHandler.subscribe(((e,t)=>{this.selected=new Set([...t.selectedItems].map((e=>parseInt(e)))),this.ui.bulk.control.hidden=0===this.selected.size,this.ui.bulk.count.hidden=0===this.selected.size,this.ui.bulk.count.textContent=`${this.selected.size} ${this.plural} selected`})),this.forms=window.jvbForm,window.jvbUploads&&window.jvbUploads.subscribe(((e,t)=>{"groups_uploaded"===e&&t.content===this.content&&this.handleGroupsUploaded(t)})),this.queue.subscribe(((e,t)=>{if(["image_upload","video_upload","document_upload"].includes(t.type)&&"operation-status"===e&&"completed"===t.status&&this.store.clearCache(),"operation-status"===e&&"completed"===t.status&&"uploads/groups"===t.endpoint&&(t.result&&t.result.group_mappings&&this.handleGroupMappings(t.result.group_mappings),this.store.clearCache()),"operation-status"===e&&"completed"===t.status&&"content_update"===t.type){if(this.store.clearCache(),!t.result||!t.result.posts)return void console.warn("Content update completed but no result.posts",t);const e=Object.keys(t.result.posts);if(0===e.length)return;this.changesStore.deleteMany(e),e.forEach((e=>this.changes.delete(e)))}}))}initSettings(){this.defaults={content:this.content,user:window.auth.getUser(),page:1,status:"all",orderby:"date",order:"desc",search:""};let e={},t=this.container.dataset.view??"grid";this.view=this.cache.get("view")??t,this.view!==t&&(this.ui.views[this.view].checked=!0),this.status=this.cache.get("status")??this.defaults.status,this.status!==this.defaults.status&&(this.ui.filters.status[this.status].checked=!0,e.status=this.status),this.orderby=this.cache.get("orderby")??this.defaults.orderby,this.orderby!==this.defaults.orderby&&(this.ui.filters.orderby[this.orderby].checked=!0,e.orderBy=this.orderby),this.order=this.cache.get("order")??this.defaults.order,this.order!==this.defaults.order&&(this.ui.filters.order[this.order].checked=!0,e.order=this.order),this.ui.filters.taxonomies&&Object.entries(this.ui.filters.taxonomies).forEach((([t,i])=>{const s=`tax_${t}`,a=this.cache.get(s);a&&(i.value=a,e[s]=a)}));let i=this.cache.get("tabNav")??"horizontal";this.ui.table.nav&&"vertical"===i&&(this.ui.table.nav.checked=!0);let s={showFilters:{element:this.ui.filters.container,default:"closed"},showUploader:{element:this.ui.uploader,default:"open"}};for(let[e,t]of Object.entries(s))if(t.element){let i=this.cache.get(e)??t.default;t.element.open="open"===i,t.element.addEventListener("toggle",(()=>{this.cache.set(e,t.element.open?"open":"closed")}))}return e}initListeners(){this.changeHandler=this.handleChange.bind(this),this.clickHandler=this.handleClick.bind(this),this.inputHandler=this.handleInput.bind(this),this.submitHandler=this.handleModalSubmit.bind(this),document.addEventListener("change",this.changeHandler),document.addEventListener("click",this.clickHandler),this.ui.filters.search&&this.ui.filters.search.addEventListener("input",this.inputHandler);for(let[e,t]of Object.entries(this.ui.modals))t.form&&t.form.addEventListener("submit",this.submitHandler)}handleModalSubmit(e){e.preventDefault();const t=e.target.closest("dialog");if(!t)return;if(t.classList.contains("create"))return void this.handleCreateSubmit(t);this.plural;this.scheduleSave(0)}async handleCreateSubmit(e){e.dataset.itemId;this.changes.size>0&&(this.cancelBackup(),await this.handleBackup());const t=await this.changesStore.getAll();if(0===t.length)return;let i={};t.forEach((e=>{const{id:t,...s}=e;i[t]=s}));let s=this.queue.addToQueue({endpoint:this.endpoint,headers:{"X-Action-Nonce":window.auth.getNonce("dash")},data:{posts:i},popup:`Creating your new ${this.singular}`,title:`Creating your new ${this.singular}`});if(!s)return;const a=e.querySelectorAll("[data-upload-field]");for(const e of a){const t=e.dataset.uploader;if(!t)continue;0!==window.jvbUploads.stores.uploads.filterByIndex({field:t}).length&&await window.jvbUploads.queueUploads("uploads",t,s)}}handleChange(e){const t=e.target.closest("[data-item-id]"),i=e.target.matches("[data-filter]"),s=e.target.matches(".bulk-action-select"),a=e.target.matches("[data-view]");if(t||i||s||a)if(this.isPopulating||!t||e.target.closest("[data-ignore], .select-item")){if(a)return this.items.clear(),void this.handleViewChange(e.target);if(s)this.handleBulkAction(e.target);else if(i)this.handleFilterChange(e.target);else if("table"===this.view){if(e.target.matches("details.multi-select"))return void this.toggleColumn(e.target.id,e.target.checked);e.target.matches(this.selectors.table.nav)&&(this.tabNav=e.target.checked,this.cache.set("tabNav",e.target.checked?"vertical":"horizontal"))}}else this.handleItemUpdate(e)}handleBulkAction(e){if(e.value.startsWith("tax-")){const t=e.options[e.selectedIndex],i=t.dataset.taxonomy,s=t.dataset.single,a=t.dataset.plural;return window.jvbSelector.openEmpty(i,s,a,(e=>this.handleBulkTaxonomy(e))),void(e.value="")}switch(e.value){case"edit":this.openBulkEditModal();break;case"publish":case"trash":case"delete":this.setBulkStatus(e.value);break;case"draft":case"restore":this.setBulkStatus("draft")}}handleBulkTaxonomy(e){e.termIds.length&&this.selected.size&&(this.selected.forEach((t=>{const i=this.store.get(t);if(!i)return;const s=(i.taxonomies?.[e.taxonomy]||[]).map((e=>e.id)),a=[...new Set([...s,...e.termIds])];this.updateItem(t,e.taxonomy,a)})),this.savePosts(`Adding ${e.terms.length} ${e.taxonomy} to ${this.selected.size} ${this.plural}...`).then((()=>{})),this.selectionHandler.clearSelection())}handleItemUpdate(e){let t=window.targetCheck(e,"[data-item-id]");if(!t)return;const i=e.target.closest('[data-field-type="repeater"], [data-field-type="tag-list"]');let s,a;if(i)s=i.dataset.field,a=this.forms.getFieldValue(i);else{let t=e.target.closest("[data-field]");s=t.dataset.field,a=this.forms.getFieldValue(e.target)}t.dataset.itemId.split(",").forEach((e=>{this.updateItem(e,s,a)}))}updateItem(e,t,i){if(this.isPopulating)return;const s=this.store.get(e);if(s){const a=s.fields?.[t]??s[t];if(null===window.getDifferences.map(a,i)){if(this.changes.has(e)){delete this.changes.get(e)[t];0===Object.keys(this.changes.get(e)).filter((e=>"id"!==e&&"content"!==e)).length&&(this.changes.delete(e),this.changesStore.delete(e))}return}}this.changes.has(e)||this.changes.set(e,{id:e,content:this.content}),this.changes.get(e)[t]=i,this.scheduleBackup(),"number"!=typeof e&&String(e).includes("group")||this.scheduleSave()}scheduleBackup(){window.debouncer.schedule(`changes-${this.content}`,(async()=>{this.changes.size>0&&await this.handleBackup()}),2e3)}cancelBackup(){window.debouncer.cancel(`changes-${this.content}`)}async handleBackup(){const e=Array.from(this.changes.values());this.changes.clear();const t=e.map((e=>e.id)),i=await Promise.all(t.map((e=>this.changesStore.get(e)))),s=e.map(((e,t)=>i[t]?window.deepMerge(i[t],e):e));await this.changesStore.saveMany(s)}scheduleSave(e=1e4){window.debouncer.schedule(`save-${this.content}`,(async()=>{this.changes.size>0&&(this.cancelBackup(),await this.handleBackup()),await this.savePosts("",!1)}),e)}handleFilterChange(e){let t=e.dataset.filter;return"date"===t&&"custom"===e.value?(e.value="",void this.modals.date.handleOpen()):"date"===t&&""!==e.value?(this.setFilter("date-filter",e.value),this.deleteFilter("dateFrom"),this.deleteFilter("dateTo"),void this.checkHideFilters()):("taxonomies"===t&&(t=`tax_${e.dataset.taxonomy}`),void this.setFilter(t,e.value))}checkHideFilters(){const e=this.store.filters,t=Object.entries(e).some((([e,t])=>!["content","user","page"].includes(e)&&(this.defaults[e]!==t&&""!==t&&null!==t)));this.ui.buttons.clearFilters.hidden=!t}clearAllFilters(){let e=this.store.filters;this.store.clearFilters();for(let[t,i]of Object.entries(e))this.cache.remove(t),this.deleteFilter(t,i);this.a11y.announce("All filters cleared")}handleCustomDateSelection(){if(this.ui.modals.date.month&&this.ui.modals.date.month.value){const[e,t]=this.ui.modals.date.month.value.split("-"),i=`${e}-${t}-01`,s=new Date(e,parseInt(t),0).getDate(),a=`${e}-${t}-${String(s).padStart(2,"0")}`;this.setFilter("dateFrom",i),this.setFilter("dateTo",a),this.deleteFilter("date-filter"),this.ui.modals.date.month.value=""}else this.ui.modals.date.start&&this.ui.modals.date.start.value&&this.ui.modals.date.end&&this.ui.modals.date.end.value&&(this.setFilter("dateFrom",this.ui.modals.date.start.value),this.setFilter("dateTo",this.ui.modals.date.end.value),this.deleteFilter("date-filter"),this.ui.modals.date.start.value="",this.ui.modals.date.end.value="");this.checkHideFilters()}handleViewChange(e){this.view=e.dataset.view,this.cache.set("view",this.view),this.render()}handleClick(e){if(e.target.matches(".clear-search"))return void this.deleteFilter("search","");const t=e.target.closest("[data-action]");return t?(e.preventDefault(),void this.handleActionButton(t)):e.target.matches(".apply-date-filter")?(this.handleCustomDateSelection(),void this.modals.date.handleClose()):void(e.target.matches(this.selectors.buttons.create)&&this.openCreateModal())}openCreateModal(){this.forms.registerForm(this.ui.modals.create.form,{cache:!1}),this.ui.modals.create.modal.dataset.itemId=window.generateID("new"),this.modals.create.handleOpen()}handleActionButton(e){const t=e.dataset.id;switch(e.dataset.action){case"edit":this.openEditModal(t);break;case"delete":confirm("Delete this item? This cannot be undone")&&(this.updateItem(t,"post_status","delete"),window.fade(e.closest(".item"),!1),this.savePosts(`Permanently deleting ${this.singular}...`).then((()=>{})),this.store.delete(t));break;case"trash":"trash"===this.status?confirm("Delete this item? This cannot be undone")&&(this.updateItem(t,"post_status","delete"),window.fade(e.closest(".item"),!1),this.savePosts(`Permanently deleting ${this.singular}...`).then((()=>{})),this.store.delete(t)):(this.updateItem(t,"post_status","trash"),window.fade(e.closest(".item"),!1),this.savePosts(`Sending ${this.singular} to trash...`).then((()=>{})));break;case"bulk-edit":this.selected.size>0&&this.openBulkEditModal();break;case"bulk-delete":this.handleBulkDelete();break;case"refresh":this.store.clearCache(),this.store.fetch();break;case"clear-filters":this.clearAllFilters()}}handleBulkDelete(){let e="trash"===this.status;if(this.selected.size>0&&confirm(`${e?"Permanently delete":"Send"} ${this.selected.size} ${1===this.selected.size?this.singular:this.plural}${e?"":"to trash"}?`)){this.selected.forEach((t=>{this.store.delete(t),this.updateItem(t,"post_status",e?"delete":"trash")}));let t=e?`Permanently deleting ${this.selected.size} ${1===this.selected.size?this.singular:this.plural}`:`Sending ${this.selected.size} ${1===this.selected.size?this.singular:this.plural} to trash`;this.savePosts(t).then((()=>{})),this.selectionHandler.clearSelection()}}handleInput(e){e.preventDefault(),e.stopPropagation();let t=e.target.value.trim(),i=`${this.content}-search`;0!==t.length?window.debouncer.schedule(i,(()=>{this.a11y.announce(`Searching for "${t}"...`),this.store.setFilters({search:t,page:1})}),300):this.deleteFilter("search","")}handleKeys(e){if(this.tabNav&&"Tab"===e.key){e.preventDefault();const t=e.target.closest("[data-field]"),i=e.target.closest("tr");if(!t||!i)return;const s=t.dataset.field,a=e.shiftKey;let l=this.findNextEditableRow(i,a);l||(l=this.wrapToRow(i,a)),l&&this.focusFieldInRow(l,s,a)}}findNextEditableRow(e,t=!1){let i=t?e.previousElementSibling:e.nextElementSibling;for(;i&&!this.isEditableRow(i);)i=t?i.previousElementSibling:i.nextElementSibling;return i}wrapToRow(e,t=!1){if(this.isTimeline){const i=e.closest("tbody");if(!i)return null;const s=Array.from(i.querySelectorAll("tr")).filter((e=>this.isEditableRow(e)));return t?s[s.length-1]:s[0]}{if(!this.ui.table.body)return null;const e=Array.from(this.ui.table.body.querySelectorAll("tr")).filter((e=>this.isEditableRow(e)));return t?e[e.length-1]:e[0]}}isEditableRow(e){return!e.closest("thead")&&!e.closest("tfoot")&&(this.isTimeline?e.classList.contains("shared")||e.classList.contains("timeline-point"):!!e.dataset.itemId)}focusFieldInRow(e,t,i=!1){const s=e.querySelector(`[data-field="${t}"]`);if(!s)return;const a=this.findFocusableInput(s);if(a){a.focus(),a.select&&"text"===a.type&&a.select();const e=i?"next":"previous";this.a11y?.announce(`Moved to ${t} in ${e} row`)}}findFocusableInput(e){const t=['input:not([type="hidden"]):not([disabled])',"textarea:not([disabled])","select:not([disabled])","button:not([disabled])"];for(const i of t){const t=e.querySelector(i);if(t)return t}return null}openEditModal(e){let t=this.store.get(parseInt(e));t&&(this.activeItem=t.id,this.ui.modals.edit.modal.dataset.itemId=e,this.ui.modals.edit.modal.dataset.content=this.content,this.ui.modals.edit.h2.textContent=`Editing ${""===t.fields.post_title?this.singular:t.fields.post_title}`,this.ui.modals.edit.form.dataset.formId=`edit-${e}`,this.forms.registerForm(this.ui.modals.edit.form,{cache:!1,autoUpload:!0}),this.isPopulating=!0,this.populate.populate(this.ui.modals.edit.form,t),requestAnimationFrame((()=>{requestAnimationFrame((()=>{this.isPopulating=!1}))})),this.modals.edit.handleOpen())}openBulkEditModal(){window.removeChildren(this.ui.modals.bulkEdit.selected),this.ui.modals.edit.form.reset(),window.chunkIt(this.selected,(t=>{let i=this.store.get(parseInt(t));if(i)return e.push(i.id),window.jvbTemplates.create("bulkItem",i)}),(e=>this.ui.modals.bulkEdit.selected.append(e))).then((()=>{}));let e=Array.from(this.selected).map((e=>this.store.get(parseInt(e)))).filter(Boolean);this.ui.modals.bulkEdit.modal.dataset.itemId=e.join(","),this.ui.modals.bulkEdit.h2&&(this.ui.modals.bulkEdit.h2.textContent=this.selected.size),this.modals.bulkEdit.handleOpen(),this.forms.registerForm(this.ui.modals.bulkEdit.form,{cache:!1}),this.isPopulating=!0,this.populate.populate(this.ui.modals.edit.form,item),requestAnimationFrame((()=>{requestAnimationFrame((()=>{this.isPopulating=!1}))}))}async savePosts(e="",t=!1){this.changes.size>0&&(this.cancelBackup(),await this.handleBackup());let i=await this.changesStore.getAll();if(0===i.length)return;if(i=this.validateChanges(i),0===i.length)return;""===e&&(e=`Saving ${i.length} ${1===i.length?this.singular:this.plural}`);let s={},a=[];i.forEach((e=>{let t=e.id;const{id:i,...l}=e;s[t]=l,e.post_status&&this.shouldRemoveItemUI(e.post_status)&&a.push(t)})),a.length>0&&this.removeItems(a);let l={endpoint:this.endpoint,headers:{"X-Action-Nonce":window.auth.getNonce("dash")},data:{posts:s},delay:t,popup:"Saving changes",title:e};this.queue.addToQueue(l)}validateChanges(e){return e.reduce(((e,t)=>{const{id:i,content:s,...a}=t,l=this.store.get(i);if(!l)return e.push(t),e;const n={id:i,content:s};let o=!1;for(const[e,t]of Object.entries(a)){const i=l.fields?.[e]??l[e];null!==window.getDifferences.map(i,t)&&(n[e]=t,o=!0)}return o?e.push(n):(this.changes.delete(i),this.changesStore.delete(i)),e}),[])}setBulkStatus(e){if(!["publish","draft","trash","delete"].includes(e))return;let t,i=[];if(this.selected.forEach((t=>{i.push(t),this.updateItem(t,"post_status",e)})),"delete"===e)t="Deleting";else t=window.uppercaseFirst(e)+"ing";this.shouldRemoveItemUI(e)&&this.removeItems(i),this.selectionHandler.clearSelection(),this.savePosts(`${t} ${i.length} ${1===i.length?this.singular:this.plural}...`).then((()=>{}))}render(){const e=this.store.getFiltered();if(0!==e.length){switch(this.view){case"grid":this.renderGrid(e);break;case"table":this.renderTable(e).then((()=>{}));break;case"list":this.renderList(e)}this.updateUI()}else this.renderEmpty()}updateUI(){if(this.ui.bulk.action){let e=!1,t=this.ui.bulk.action.querySelector('[value="edit"]'),i=this.status;"trash"===i&&t?(window.removeChildren(this.ui.bulk.action),e=window.jvbTemplates.create("trashOptions")):"trash"===i||t||(window.removeChildren(this.ui.bulk.action),e=window.jvbTemplates.create("notTrashOptions")),e&&e.querySelectorAll("option").forEach(((e,t)=>{0===t&&(e.checked=!0),this.ui.bulk.action.append(e)})),this.ui.bulk.action.value=""}this.selected.size>0&&this.selectionHandler.updateSelectionUI()}renderEmpty(){this.toggleTable(!1),window.removeChildren(this.ui.grid);const e=window.jvbTemplates.create("emptyState");e&&(this.ui.grid.append(e),this.a11y.announceItems(0,!1,!1))}toggleTable(e=!0){if(this.ui.table.selectedColumns&&(this.ui.table.selectedColumns.hidden=!e),e&&!this.ui.table.form){let e=window.jvbTemplates.create("contentTable");this.container.append(e),this.ui.table=window.uiFromSelectors(this.selectors.table),this.ui.table.columns=this.container.querySelectorAll(this.selectors.table.columns)}this.ui.table.form&&(this.ui.table.form.hidden=!e,e||this.forms.clearForm(this.ui.table.form.dataset.formId),this.ui.table.body&&window.removeChildren(this.ui.table.body)),this.keyHandler=this.handleKeys.bind(this),e?document.addEventListener("keydown",this.keyHandler):document.removeEventListener("keydown",this.keyHandler)}renderGrid(e){window.removeChildren(this.ui.grid),this.toggleTable(!1),this.ui.grid.classList.remove("list-view"),this.ui.grid.classList.add("grid-view"),window.chunkIt(e,(e=>this.renderGridItem(e)),(e=>this.ui.grid.append(e))).then((()=>{}))}renderList(e){window.removeChildren(this.ui.grid),this.toggleTable(!1),this.ui.grid.classList.remove("grid-view"),this.ui.grid.classList.add("list-view"),window.chunkIt(e,(e=>this.renderListItem(e)),(e=>this.ui.grid.append(e))).then((()=>{}))}async renderTable(e){this.toggleTable(),window.removeChildren(this.ui.grid),await window.chunkIt(e,(e=>this.renderTableItem(e)),(e=>{this.ui.table.body?this.ui.table.body.append(e):this.ui.table.table.insertBefore(e,this.ui.table.foot)}),5),requestAnimationFrame((()=>{window.jvbSelector?.scanExistingFields(this.ui.table.table)}))}renderGridItem(e){let t=window.jvbTemplates.create("gridView",e);return this.items.set(e.id,t),t}renderListItem(e){let t=window.jvbTemplates.create("listView",e);return this.items.set(e.id,t),t}renderTableItem(e){let t=window.jvbTemplates.create("tableView",e);return this.items.set(e.id,t),t}toggleColumn(e,t){this.ui.table.table.querySelectorAll(`.${e}`).forEach((e=>{e.hidden=!t}))}handleGroupsUploaded(e){const{posts:t,fieldId:i}=e;let s=window.jvbUploads,a=(s.fields.get(i),[]);t.forEach((e=>{const t={id:e.groupId,title:e.fields.post_title||`New ${this.singular}`,status:"draft",date:(new Date).toISOString(),modified:(new Date).toISOString(),thumbnail:null,icon:this.content,taxonomies:{},fields:e.fields,images:{}};e.images.forEach(((e,i)=>{let a=e.upload_id;0===i&&(t.fields.post_thumbnail=e);let l=s.stores.uploads.get(a);l&&(t.images[a]={"image-alt-text":"","image-caption":"","image-title":l.fields.originalName,medium:s.createPreviewUrl(s.formatFile(l))})})),a.push(t)})),this.store.saveMany(a).then((()=>this.render())),this.a11y.announce(`${t.length} ${1===t.length?this.singular:this.plural} created. Waiting for server confirmation...`)}handleGroupMappings(e){for(const[t,i]of Object.entries(e)){let e={};this.changes.has(t)&&(e=this.changes.get(t),this.changes.delete(t));let s=this.changesStore.get(t)??{};(e.size>0||s.size>0)&&(e=window.deepMerge(s,e),this.changes.set(i,e),this.scheduleBackup())}}shouldRemoveItemUI(e){return"all"===this.status&&!["publish","draft"].includes(e)||e!==this.store.filters.status}removeItems(e){e.forEach((e=>{if(this.items.has(e)){let t=this.items.get(e);t&&window.fade(t,!1)}}))}setFilters(e){for(let[t,i]of Object.entries(e)){if(!this.allowedFilters.includes(t)){delete e[t];continue}this.cache.set(t,i);let s=this.findFilterEl(t);this.setElValue(s,i)}this.store.setFilters(e)}setFilter(e,t){if(!this.allowedFilters.includes(e))return;this.cache.set(e,t),"status"===e&&(this.status=t),"orderby"===e&&(this.orderby=t),"order"===e&&(this.order=t);let i=this.findFilterEl(e,t);this.setElValue(i,t),this.store.setFilter(e,t)}deleteFilter(e,t){if(!this.allowedFilters.includes(e))return;if(Object.hasOwn(this.defaults,e))return void this.setFilter(e,this.defaults[e]);let i=this.findFilterEl(e,t);this.setElValue(i,!1),this.cache.remove(e),this.setFilter(e,"")}setElValue(e,t){if(e){if(!t)return["SELECT","TEXTAREA"].includes(e.tagName)&&(e.value=""),["text","search"].includes(e.type)&&(e.value=""),void("radio"===e.type&&(e.checked=!1));["SELECT","TEXTAREA"].includes(e.tagName)&&(e.value=t),["text","search"].includes(e.type)&&(e.value=t),"radio"===e.type&&(e.checked=!0)}}findFilterEl(e,t){if(["date-filter","dateFrom","dateTo"].includes(e)){switch(e){case"date-filter":e="month";break;case"dateFrom":e="start";break;case"dateTo":e="end"}return this.ui.modals.date[e]}if(e.includes("tax_")){const t=e.replace("tax_",""),i=this.ui.filters.taxonomies?.[t];return i||(console.warn("Taxonomy filter element not found:",t),null)}if(!Object.hasOwn(this.ui.filters,e))return console.warn("Filter el not found: ",e),!1;let i=this.ui.filters[e];if("object"==typeof i){if(!Object.hasOwn(this.ui.filters[e],t))return!1;i=this.ui.filters[e][t]}return i}resetForm(e){e.querySelectorAll('input[type="hidden"], input[type="text"], input[type="number"], input[type="email"], input[type="url"], textarea').forEach((e=>{e.value=""})),e.querySelectorAll('input[type="checkbox"], input[type="radio"]').forEach((e=>{e.checked=!1})),e.querySelectorAll("select").forEach((e=>{e.selectedIndex=0})),e.querySelectorAll(".selected-items").forEach((e=>{window.removeChildren(e)})),e.querySelectorAll(".item-grid.preview").forEach((e=>{window.removeChildren(e)}))}destroy(){window.debouncer.cancel(`changes-${this.content}`),this.changes.size>0&&(this.changesStore.saveMany(this.changes).then((()=>{})),this.changes.clear()),this.timelineSortables&&(this.timelineSortables.forEach((e=>e.destroy())),this.timelineSortables=[]);for(let[e,t]of Object.entries(this.ui.modals))t.form&&t.form.removeEventListener("submit",this.submitHandler);document.removeEventListener("click",this.clickHandler),document.removeEventListener("change",this.changeHandler),this.ui.filters.search&&this.ui.filters.search.removeEventListener("input",this.handleInput)}}document.addEventListener("DOMContentLoaded",(async function(){window.auth.subscribe((t=>{if("auth-loaded"===t){let t=document.querySelector("[data-content]");t&&!Object.hasOwn(t.dataset,"ignore")&&(window.crudManager=new e({content:t.dataset.content}))}}))}))})();
\ No newline at end of file
diff --git a/base/seo.php b/base/seo.php
index 5fa7d08..bd248f9 100644
--- a/base/seo.php
+++ b/base/seo.php
@@ -119,7 +119,7 @@
 				'paymentAccepted' => 'payment_accepted',
 			],
 			'overrides' => [
-				'additionalType' => 'https://schema.org/TattooParlor',
+				'additionalTypeTrait' => 'https://schema.org/TattooParlor',
 			]
 		]
 	]
diff --git a/base/taxonomies.php b/base/taxonomies.php
index 6143668..02d3c6e 100644
--- a/base/taxonomies.php
+++ b/base/taxonomies.php
@@ -14,7 +14,7 @@
  *         - verify_entry      = (bool) if true, users not already attached to this taxonomy need to be approved before entry.
  *         - approve_new        = (bool) if true, admin/verified users need to approve before 'live'
  *         - track_changes        = (bool) if true, table is created to track historical changes
- *         - for_content        = (array) of post type slugs, as defined in JVB_CONTENT
+ *         - for_content        = (array) of post type slugs, as defined in Registrar instances
  *         - fields            = (array) of custom field definitions, from inc/managers/Meta.php
  *             -> add use_in_stats (bool) to use the field in user statistics
  */
diff --git a/base/users.php b/base/users.php
index 483882f..c7abd64 100644
--- a/base/users.php
+++ b/base/users.php
@@ -13,8 +13,8 @@
  *                                 ]
  *         - manage_others    = (array) of post types this role can manage other users items
  *         - can_register        = (bool) if true, this user can self register
- *         - profile            = (string) associated post type to treat as archive page, as registered in JVB_CONTENT
- *         - register_fields    = (array) of field names available in registration, as defined in settings OR associated profile JVB_CONTENT
+ *         - profile            = (string) associated post type to treat as archive page, as registered in Registrar
+ *         - register_fields    = (array) of field names available in registration, as defined in settings OR associated profile post Registrar
  *         - approve_new        = (bool) if true, user content is hidden until verified by admin/other verified users
  *         - keep_stats        = (bool) if true, creates a statistics table that tracks how much content is created per user per day
  *         - settings            = (array) of fields users can modify
diff --git a/checks.php b/checks.php
index 86c7526..60474c3 100644
--- a/checks.php
+++ b/checks.php
@@ -1,171 +1,16 @@
 <?php
 
 use JVBase\managers\Cache;
+use JVBase\registrar\Registrar;
 use JVBase\utility\Features;
 
 if (!defined('ABSPATH')) {
 	exit;
 }
-function jvbCheck(string $key, $config = [])
+function jvbCheck(string $key, $config = []):bool
 {
     return (array_key_exists($key, $config) && $config[$key] === true);
 }
-/**
- * Tests whether this site needs forum functionality
- * @return bool
- */
-function jvbSiteHasForum():bool
-{
-    return jvbCheck('forum', JVB_MEMBERSHIP);
-}
-
-function jvbSiteUsesSquare():bool
-{
-	return jvbSiteHasIntegrations() && jvbCheck('square', JVB_SITE['integrations']);
-}
-function jvbSiteUsesGoogleBusiness():bool
-{
-	return jvbSiteUsesGMB();
-}
-
-function jvbSiteHasFavourites():bool
-{
-	return jvbCheck('favourites', JVB_SITE);
-}
-
-/**
- * Tests whether this site has a custom dashboard
- * @return bool
- */
-function jvbSiteHasDashboard():bool
-{
-    return jvbCheck('dashboard', JVB_SITE);
-}
-
-function jvbSiteHasKarma():bool
-{
-    global $jvb_karma;
-    return (!empty($jvb_karma['content']) || !empty($jvb_karma['taxonomy']));
-}
-
-function jvbSiteHasSupport():bool
-{
-    return  jvbCheck('has_support', JVB_SITE);
-}
-
-function jvbSiteHasInvitations():bool
-{
-    return jvbCheck('can_invite', JVB_MEMBERSHIP);
-}
-
-function jvbSiteHasMemberApproval():bool
-{
-    return jvbCheck('member_verified', JVB_MEMBERSHIP);
-}
-
-function jvbSiteHasMembership():bool
-{
-    return jvbCheck('has_membership', JVB_SITE);
-}
-
-function jvbSiteHasResponse():bool
-{
-    global $jvb_responses;
-    return (!empty($jvb_responses['content']) || !empty($jvb_responses['taxonomy']));
-}
-
-function jvbSiteUsesFeedBlock():bool
-{
-    return jvbCheck('use_feed_block', JVB_SITE);
-}
-
-function jvbSiteCanFavourite():bool
-{
-    return jvbCheck('enthusiast', JVB_SITE) && jvbCheck('favourites', JVB_SITE);
-}
-
-function jvbSiteUsesUmami():bool
-{
-    return jvbSiteHasIntegrations() && jvbCheck('umami', JVB_SITE['integrations']);
-}
-function jvbSiteUsesCloudflare():bool
-{
-	return jvbSiteHasIntegrations() && jvbCheck('cloudflare', JVB_SITE['integrations']);
-}
-
-function jvbSiteUsesFacebook():bool
-{
-	return jvbSiteHasIntegrations() && jvbCheck('facebook', JVB_SITE['integrations']);
-}
-function jvbSiteUsesInstagram():bool
-{
-	return jvbSiteHasIntegrations() && jvbCheck('instagram', JVB_SITE['integrations']);
-}
-function jvbSiteUsesBluesky():bool
-{
-	return jvbSiteHasIntegrations() && jvbCheck('bluesky', JVB_SITE['integrations']);
-}
-function jvbSiteUsesGMB():bool
-{
-	return jvbSiteHasIntegrations() && jvbCheck('gmb', JVB_SITE['integrations']);
-}
-function jvbSiteUsesHelcim():bool
-{
-	return jvbSiteHasIntegrations() && jvbCheck('helcim', JVB_SITE['integrations']);
-}
-
-function jvbSiteUses(string $key):bool
-{
-	return jvbCheck($key, JVB_SITE);
-}
-
-function jvbSiteUsesMaps():bool
-{
-	return true;
-}
-
-function jvbSiteHasTermContent():bool
-{
-    return (!empty(array_keys(array_filter(JVB_TAXONOMY, function ($taxonomy) {
-        return jvbCheck('is_content', $taxonomy);
-    }))));
-}
-
-function jvbSiteHasNotifications():bool
-{
-    return jvbCheck('notifications', JVB_MEMBERSHIP);
-}
-
-function jvbUserCanCreate(int $userID = 0):bool
-{
-    $user = ($userID === 0) ? wp_get_current_user() : get_userdata($userID);
-    $roles = array_intersect(
-        jvbRolesWithDashboard(),
-        array_map(function ($role) {
-            return jvbNoBase($role);
-        },
-            $user->roles)
-    );
-    $creatable = [];
-    foreach ($roles as $role) {
-        $creatable = array_merge($creatable, JVB_USER[$role]['can_create']??[]);
-    }
-
-    $creatable = jvbExtractUserContent($creatable);
-    return !empty($creatable);
-}
-
-function jvbRolesWithDashboard():array
-{
-    return array_keys(array_filter(JVB_USER, function ($role) {
-        return jvbCheck('has_dashboard', $role);
-    }));
-}
-
-function jvbSiteHasTermApproval():bool
-{
-    return jvbCheck('terms_approval', JVB_MEMBERSHIP);
-}
 
 function jvbUserIsVerified():bool
 {
@@ -190,35 +35,20 @@
 	return $types;
 }
 
-function isJVBUserType():bool
-{
-	return (is_singular(jvbUserTypes()) || is_post_type_archive(jvbUserTypes()));
-}
-
-function contentIsJVBUserType(string $content):bool
-{
-	$content = jvbCheckBase($content);
-	return in_array($content, jvbUserTypes());
-}
-
-
 function isJVBContentTax():bool
 {
-	return is_tax(jvbContentTaxonomies());
+	return is_tax(array_map(function ($tax) {
+		return jvbCheckBase($tax);
+	}, Registrar::getFeatured('is_content', 'term')));
 }
 
 function taxIsJVBContentTax($tax):bool
 {
-	$tax = jvbCheckBase($tax);
-	return in_array($tax, jvbContentTaxonomies());
+	$allowed = Registrar::getFeatured('is_content', 'term');
+	$tax = jvbNoBase($tax);
+	return in_array($tax, $allowed);
 }
 
-function jvbSiteHasIntegrations():bool
-{
-	return array_key_exists('integrations', JVB_SITE) && !empty(JVB_SITE['integrations']);
-}
-
-
 function jvbIsOpen():bool
 {
 
@@ -237,28 +67,6 @@
 }
 
 
-function jvbIsValidType(string $type):bool {
-
-	return (array_key_exists($type, JVB_CONTENT) ||
-		array_key_exists($type, JVB_TAXONOMY) ||
-		array_key_exists($type, JVB_USER) ||
-		$type === 'options');
-}
-
-function jvbGetObjectType(string $type):string {
-	if (array_key_exists($type, JVB_CONTENT)) {
-		return 'post';
-	} elseif (array_key_exists($type, JVB_TAXONOMY)) {
-		return 'term';
-	} elseif (array_key_exists($type, JVB_USER)) {
-		return 'user';
-	} elseif ($type === 'options') {
-		return 'options';
-	}
-	return '';
-}
-
-
 function jvbTermHasPosts(int $termID, string $taxonomy):bool
 {
 	$cache = Cache::for('termUtility', 30*60)->connect('taxonomy');
diff --git a/globals.php b/globals.php
deleted file mode 100644
index 38b518e..0000000
--- a/globals.php
+++ /dev/null
@@ -1,600 +0,0 @@
-<?php
-if (!defined('ABSPATH')) {
-	exit;
-}
-
-use JVBase\utility\Features;
-
-function jvbGlobalDirectories():array
-{
-    $directories = get_option(BASE.'_global_directories');
-    if (JVB_TESTING) {
-        $directories = false;
-    }
-    if ($directories === false) {
-        $directories = array_merge(
-            array_fill_keys(array_keys(array_filter(JVB_CONTENT, function ($content) {
-                return jvbCheck('show_directory', $content);
-            })), 'content'),
-            array_fill_keys(array_keys(array_filter(JVB_TAXONOMY, function ($taxonomy) {
-                return jvbCheck('show_directory', $taxonomy);
-            })), 'tax')
-        );
-//        update_option(BASE.'_global_directories', $directories);
-    }
-    return $directories;
-}
-
-function jvbGlobalKarma():array
-{
-    $karma = get_option(BASE.'_global_karma');
-    if (JVB_TESTING) {
-        $karma = false;
-    }
-    if ($karma === false) {
-        $karma = [
-            'content'    => array_keys(array_filter(JVB_CONTENT, function ($content) {
-                return isset($content['karma']) && $content['karma'] === true;
-            })),
-            'taxonomy'    => array_keys(array_filter(JVB_TAXONOMY, function ($taxonomy) {
-                return isset($taxonomy['karma']) && $taxonomy['karma'] === true;
-            }))
-        ];
-//        update_option(BASE.'_global_karma');
-    }
-    return $karma;
-}
-
-function jvbGlobalTaxonomyFor():array
-{
-    $taxonomy_for = get_option(BASE.'_global_taxonomy_for');
-    if (JVB_TESTING) {
-        $taxonomy_for = false;
-    }
-    if ($taxonomy_for === false) {
-        $taxonomy_for = [];
-        foreach (JVB_TAXONOMY as $tax => $config) {
-            $taxonomy_for[$tax] = $config['for_content'];
-        }
-//        update_option(BASE.'_global_taxonomy_for', $taxonomy_for);
-    }
-    return $taxonomy_for;
-}
-
-function jvbGlobalFeedContent():array {
-    $content = get_option(BASE.'_global_feed_content');
-    if (JVB_TESTING) {
-        $content = false;
-    }
-    if (!$content) {
-        $taxonomy = array_fill_keys(array_keys(array_filter(JVB_TAXONOMY, function ($tax) {
-            return jvbCheck('is_content', $tax);
-        })), 'tax');
-        $content = array_fill_keys(array_keys(array_filter(JVB_CONTENT, function ($c) {
-            return jvbCheck('show_feed', $c);
-        })), 'content');
-        $content = array_merge($content, $taxonomy);
-        update_option(BASE.'_global_feed_content', $content);
-    }
-    return $content;
-}
-
-function jvbBasedFeedContent():array
-{
-	$content = get_option(BASE.'_based_feed_content');
-	if (!$content) {
-		$content = array_map(function ($c) {
-			return jvbCheckBase($c);
-		}, jvbGlobalFeedContent());
-		update_option(BASE.'based_feed_content', $content);
-	}
-	return $content;
-}
-
-function jvbGlobalResponses():array
-{
-    $responses = get_option(BASE.'_global_responses');
-    if (JVB_TESTING) {
-        $responses = false;
-    }
-    if ($responses === false) {
-        $responses = [
-            'content'    => array_keys(array_filter(JVB_CONTENT, function ($content) {
-                return isset($content['responses']) && $content['responses'] === true;
-            })),
-            'taxonomy'    => array_keys(array_filter(JVB_TAXONOMY, function ($taxonomy) {
-                return isset($taxonomy['responses']) && $taxonomy['responses'] === true;
-            }))
-        ];
-//        update_option(BASE.'_global_responses', $responses);
-    }
-    return $responses;
-}
-
-function jvbGetFields(string $type, ?string $object_type = null): array
-{
-	return JVBase\registry\FieldRegistry::getInstance()->getFields($type, $object_type);
-}
-
-
-function jvbGetValidType(string $type) {
-	return str_replace('-', '_', (jvbNoBase($type)));
-}
-function jvbGetSections(string $type, ?string $object_type = null):array
-{
-	$type = jvbGetValidType($type);
-
-	if (!jvbIsValidType($type)) {
-		return [];
-	}
-	if (!$object_type) {
-		$object_type = jvbGetObjectType($type);
-	}
-
-	$optionKey = BASE.$type.'_'.$object_type.'_sections';
-	$sections = get_option($optionKey, false);
-	$sections = false;
-	if (JVB_TESTING) {
-		$sections = false;
-	}
-	if (!$sections) {
-		$fields = jvbGetFields($type, $object_type);
-		$sections = ['basic' => 'Basic'];
-
-		foreach ($fields as $field) {
-			if (array_key_exists('section', $field)) {
-				$sections[sanitize_title($field['section'])] = jvbSectionTitle($field['section']);
-			}
-		}
-
-		update_option($optionKey, $sections);
-	}
-
-	return $sections;
-}
-
-function jvbSectionTitle(string $section):string
-{
-	switch ($section) {
-		case 'gmb':
-			return 'Google My Business';
-		case 'square-config':
-			return 'Square Options';
-		case 'sync':
-			return 'Sync Settings';
-		default:
-			return ucwords(str_replace('-', ' ', $section));
-	}
-}
-function jvbSectionIcon(string $section):string
-{
-	switch ($section) {
-		case 'gmb':
-			return 'storefront';
-		case 'square-config':
-			return 'square-logo';
-		case 'sync':
-			return 'plugs-connected';
-		default:
-			return '';
-	}
-}
-
-function jvbSectionDescription(string $section): string
-{
-	switch ($section) {
-		case 'gmb':
-			return 'These optional fields help optimize your Google Business listing.';
-		case 'variations':
-			return 'If you have different purchase options for this item, list them here. Example: small, medium, large';
-		default:
-			return '';
-	}
-}
-function jvbRegisterCommonFields(array $common)
-{
-	$fields = [];
-	foreach ($common??[] as $field => $c) {
-		if (!is_numeric($field)) {
-			$f = JVBase\registry\MetaFieldRegistrar::getCommonMeta($field, $c);
-		} else {
-			$f = JVBase\registry\MetaFieldRegistrar::getCommonMeta($c);
-		}
-		$fields = array_merge($fields, $f);
-	}
-	return $fields;
-}
-
-function jvbGetAllDashboardPages()
-{
-    $manageableContent = get_option(BASE.'all_dashboard_pages');
-    if (JVB_TESTING) {
-        $manageableContent = false;
-    }
-    if ($manageableContent === false) {
-
-        $manageableContent = [];
-        $bios = [];
-        foreach (JVB_USER as $role => $config) {
-            $manageableContent = array_merge($manageableContent, jvbRolePages($role));
-        }
-
-		if (Features::forSite()->has('referrals')) {
-			$manageableContent[] = 'referrals';
-		}
-        foreach (JVB_TAXONOMY as $tax => $config) {
-            if (jvbCheck('is_content', $config)) {
-                $manageableContent[] = strtolower($config['plural']);
-            }
-        }
-
-        if (jvbCheck('can_invite', JVB_MEMBERSHIP)) {
-            $manageableContent[] = 'invites';
-        }
-
-        if (jvbCheck('term_approval', JVB_MEMBERSHIP)) {
-            $manageableContent[] = 'approvals';
-        }
-
-        if (jvbSiteHasForum()) {
-            $manageableContent[] = 'news';
-        }
-
-        if (jvbCheck('member_content', JVB_MEMBERSHIP)) {
-            $manageableContent[] = 'metrics';
-        }
-
-        if (!empty($bios)) {
-            $manageableContent[] = 'bio';
-        }
-
-        if (jvbCheck('favourites', JVB_SITE)) {
-            $manageableContent[] = 'favourites';
-        }
-
-        if (jvbSiteHasKarma()) {
-            $manageableContent[] = 'karmic-score';
-        }
-
-        if (jvbSiteHasNotifications()) {
-            $manageableContent[] = 'notifications';
-        }
-
-        if (jvbSiteHasSupport()) {
-            $manageableContent[] = 'support';
-        }
-
-		if (jvbSiteHasIntegrations()) {
-			$manageableContent[] = 'integrations';
-		}
-
-        $manageableContent[] = 'admin';
-		$manageableContent = apply_filters('jvbDashboardPages', $manageableContent);
-		$manageableContent = array_unique($manageableContent);
-		sort($manageableContent);
-		$manageableContent = array_map(function ($content) {
-			return str_replace('_', '-', $content);
-		}, $manageableContent);
-		update_option(BASE.'all_dashboard_pages', $manageableContent);
-    }
-
-
-    return $manageableContent;
-}
-
-function jvbRolePages($role)
-{
-    $manageableContent = get_option(BASE.$role.'_pages');
-    if (JVB_TESTING) {
-        $manageableContent = false;
-    }
-    if ($manageableContent === false) {
-        $manageableContent = [];
-        $config = JVB_USER[$role];
-        $content = $config['can_create'];
-        $settings = $bio = false;
-        if (array_key_exists('profile', $config)) {
-            $manageableContent[] = $config['profile'];
-        }
-        $manageableContent = array_merge($manageableContent, jvbExtractUserContent($config['can_create']));
-
-
-        if (array_key_exists('has_dashboard', $config)) {
-            $manageableContent[] = 'settings';
-        }
-
-        update_option(BASE.$role.'_pages', $manageableContent);
-    }
-
-    return $manageableContent;
-}
-
-function jvbExtractUserContent(array $content):array
-{
-	// Deprecated: Use Features::forUser($role)->getCreatableContent() instead
-	_deprecated_function(__FUNCTION__, '2.0.0', 'Features::forUser($role)->getCreatableContent()');
-
-	$out = [];
-    foreach ($content as $c) {
-        if (is_array($c)) {
-            foreach ($c as $type => $contents) {
-                $out = array_merge($out, $contents);
-            }
-        } else {
-            $out = array_merge($out, [$c]);
-        }
-    }
-    return $out;
-}
-
-function jvbGlobalDirectoryInfo():array
-{
-    return get_option(BASE.'directory_list',[]);
-}
-function jvbGlobalDirectoryIds():array
-{
-    return get_option(BASE.'directory_ids',[]);
-}
-
-function jvbContentTaxContent(string $type):array
-{
-    $content = get_option(BASE.$type.'_content');
-    if (JVB_TESTING) {
-        $content = false;
-    }
-    if ($content === false) {
-        global $jvb_taxonomy_for;
-        $content = $jvb_taxonomy_for[$type] ?? [];
-
-        if (jvbCheck('associate_user_content', JVB_TAXONOMY[$type])) {
-            $userTypes = jvbUserTypes();
-            foreach ($content as $c) {
-                $c = jvbCheckBase($c);
-                if (in_array($c, $userTypes)) {
-                    $user = array_search($c, $userTypes);
-                    $content = array_merge($content, jvbExtractUserContent(JVB_USER[$user]['can_create']??[]));
-                }
-            }
-            $content = array_unique($content);
-            $content = array_filter($content, function ($c) {
-                return array_key_exists($c, jvbGlobalFeedContent());
-            });
-        }
-
-        update_option(BASE.$type.'_content', $content);
-    }
-    return $content;
-}
-
-
-function jvbContentTaxonomies():array
-{
-    $types = get_option(BASE.'content_taxonomies');
-    if (JVB_TESTING) {
-        $types = false;
-    }
-    if ($types === false) {
-        $types = array_map(function ($content) { return BASE.$content; },
-            array_keys(array_filter(JVB_TAXONOMY, function ($tax) {
-                return jvbCheck('is_content', $tax);
-            })));
-        update_option(BASE.'content_taxonomies', $types);
-    }
-    return $types;
-}
-
-function jvbContentTaxonomiesTableFields(string $type):array
-{
-    $fields = get_option(BASE.$type.'_content_fields');
-    if (JVB_TESTING) {
-        $fields = false;
-    }
-
-    if (!$fields) {
-        $all = JVB_TAXONOMY[$type]['content_table']??[];
-
-        $fields = $all['fields']??[];
-
-        $temp = [];
-        if (!empty($fields)) {
-            $index = jvbGetFields($type);
-            foreach ($fields as $field) {
-                $temp[$field] = $index[$field];
-            }
-            $all['fields'] = $temp;
-        }
-        $fields = $all;
-        update_option(BASE.$type.'_content_fields', $fields);
-    }
-    return $fields;
-}
-
-function jvbApprovalTypes():array
-{
-    return get_option(BASE.'approval_types', []);
-}
-
-//Gets the user roles of users that can create
-function jvbAuthorUsers():array
-{
-    return array_keys(array_filter(JVB_USER, function ($value) {
-        return !empty($value['can_create']??[]);
-    }));
-}
-
-function jvbNoSingleContentTypes():array
-{
-    return array_keys(array_filter(JVB_CONTENT, function ($value) {
-        return jvbCheck('hide_single', $value);
-    }));
-}
-
-function jvbTaxonomiesForContent(string $content):array
-{
-    $taxonomies = get_option(BASE.$content.'_taxonomies');
-    if (JVB_TESTING) {
-        $taxonomies = false;
-    }
-    if (!$taxonomies) {
-        global $jvb_taxonomy_for;
-        $taxonomies = [];
-        foreach ($jvb_taxonomy_for as $tax => $contentTypes) {
-            if (in_array($content, $contentTypes)) {
-                $taxonomies[] = BASE.$tax;
-            }
-        }
-        update_option(BASE.$content.'_taxonomies', $taxonomies);
-    }
-    return $taxonomies;
-}
-
-function jvbGetRandomSeed():string
-{
-    return floor(time() / 1800);
-}
-
-/**
- * The following return a slug => $config pair array of any registered types.
- */
-function jvbCalendarTableTypes():array
-{
-	//Set on activation through CheckCustomTables.php
-	return get_option(BASE.'calendar_tables');
-}
-
-function jvbCalenderTables():array
-{
-	$tables = [];
-	foreach (jvbCalendarTableTypes() as $type => $config) {
-		$tables[$type] = BASE.'calendar_'.$type;
-	}
-	return $tables;
-}
-
-function jvbKarmaTableTypes():array
-{
-	//Set on activation through CheckCustomTables.php
-	return get_option(BASE.'karma_tables');
-}
-function jvbKarmaTables():array
-{
-	$tables = [];
-	foreach (jvbKarmaTableTypes() as $type => $config) {
-		$tables[$type] = BASE.'karma_'.$type;
-	}
-	return $tables;
-}
-
-function jvbStatsTableTypes():array
-{
-	//Set on activation through CheckCustomTables.php
-	return get_option(BASE.'stats_tables');
-}
-
-function jvbStatsTables():array
-{
-	$tables = [];
-	foreach (jvbStatsTableTypes() as $type => $config) {
-		$tables[$type] = BASE.'stats_'.$type;
-	}
-	return $tables;
-}
-
-function jvbInviteTableTypes():array
-{
-	//Set on activation through CheckCustomTables.php
-	return get_option(BASE.'invite_tables');
-}
-
-function jvbInviteTables():array
-{
-	$tables = [];
-	foreach (jvbInviteTableTypes() as $type => $config) {
-		$tables[$type] = BASE.'invitations_'.$type;
-	}
-	return $tables;
-}
-
-function jvbVerifyEntryTableTypes():array
-{
-	//Set on activation through CheckCustomTables.php
-	return get_option(BASE.'verify_entry_tables');
-}
-
-
-function jvbVerifyEntryTables():array
-{
-	$tables = [];
-	foreach (jvbVerifyEntryTableTypes() as $type => $config) {
-		$contents = $config['for_content'];
-		foreach($contents as $content) {
-			$tables[$type][$content] = BASE.$content.'_'.$type.'_requests';
-		}
-	}
-	return $tables;
-}
-
-function jvbApprovalTableTypes():array
-{
-	//Set on activation through CheckCustomTables.php
-	return get_option(BASE.'approval_tables');
-}
-
-function jvbApprovalTables():array
-{
-	$tables = [];
-	foreach (jvbApprovalTableTypes() as $type => $config) {
-		$tables['requests'][$type] = BASE.'approval_'.$type.'_requests';
-		$tables['votes'][$type] = BASE.'approval_'.$type.'_votes';
-	}
-	return $tables;
-}
-
-function jvbHistoryTableTypes():array
-{
-	//Set on activation through CheckCustomTables.php
-	return get_option(BASE.'history_tables');
-}
-
-function jvbHistoryTables():array
-{
-	$tables = [];
-	foreach (jvbHistoryTableTypes() as $type => $config) {
-		$contents = $config['for_content'];
-		foreach($contents as $content) {
-			$tables[$type][$content] = BASE.'history_'.$content.'_'.$type;
-		}
-	}
-	return $tables;
-}
-
-
-function jvbSquarePOSTypes():array {
-		$enabled_types = [];
-
-		foreach (JVB_CONTENT as $post_type => $config) {
-			if (!empty($config['integrations']['square']['enabled'])) {
-				$enabled_types[] = $post_type;
-			}
-		}
-
-		return $enabled_types;
-}
-
-
-function jvbGetLabels():array {
-	$labels = get_option(BASE.'content_labels');
-	$labels = false;
-	if (!$labels) {
-		$labels = [];
-		$all = array_merge(JVB_CONTENT, JVB_TAXONOMY);
-		foreach ($all as $type => $config) {
-			$labels[$type] = [
-				'single'	=> $config['singular'],
-				'plural'	=> $config['plural']
-			];
-		}
-		update_option(BASE.'content_labels', $labels);
-	}
-	return $labels;
-}
diff --git a/inc/admin/ContentTaxonomy.php b/inc/admin/ContentTaxonomy.php
index 5064df1..fcbca46 100644
--- a/inc/admin/ContentTaxonomy.php
+++ b/inc/admin/ContentTaxonomy.php
@@ -5,6 +5,7 @@
 	exit;
 }
 
+use JVBase\registrar\Registrar;
 use JVBase\registry\TaxonomyRegistrar;
 use WP_Error;
 use Exception;
@@ -17,7 +18,7 @@
 {
     public function __construct()
     {
-		if (!jvbSiteHasTermContent()){
+		if (empty(Registrar::getFeatured('is_content', 'term'))){
 			return;
 		}
         add_filter(BASE.'handle_bulk_operation', [ $this, 'processOperation' ], 10, 3);
@@ -50,8 +51,8 @@
         // Handle individual taxonomy rebuild
         if (isset($_POST['rebuild_taxonomy']) && wp_verify_nonce($_POST['_wpnonce'], 'rebuild_taxonomy')) {
             $taxonomy = sanitize_text_field($_POST['taxonomy']);
-
-            if (isset(JVB_TAXONOMY[$taxonomy]) && (JVB_TAXONOMY[$taxonomy]['is_content'] ?? false)) {
+			$registrar = Registrar::getInstance($taxonomy));
+            if ($registrar && $registrar->hasFeature('is_content')) {
                 $results = $this->rebuildCustomTable($taxonomy);
                 // Store results in transient to display after redirect
                 set_transient('jvb_rebuild_results_' . $taxonomy, $results, 300); // 5 minutes
@@ -123,16 +124,17 @@
                     </tr>
                     </thead>
                     <tbody>
-                    <?php foreach (JVB_TAXONOMY as $slug => $config): ?>
-                        <?php if (!($config['is_content'] ?? false)) continue; ?>
-                        <?php
+                    <?php
+					$taxonomies = Registrar::getFeatured('is_content', 'term');
+					foreach ($taxonomies as $slug):
+						$registrar = Registrar::getInstance($slug);
                         $taxonomy = BASE . $slug;
                         $table_info = $this->getTableInfo($slug);
                         $term_count = wp_count_terms($taxonomy, ['hide_empty' => false]);
                         ?>
                         <tr>
                             <td>
-                                <strong><?= esc_html($config['plural'] ?? ucfirst($slug)) ?></strong><br>
+                                <strong><?= esc_html($registrar->getPlural()) ?></strong><br>
                                 <code><?= esc_html($taxonomy) ?></code>
                             </td>
                             <td>
@@ -314,8 +316,6 @@
      */
     public function rebuildCustomTable(string $taxonomy):array
     {
-        global $wpdb;
-
         $results = [
             'success' => 0,
             'errors' => 0,
@@ -324,11 +324,13 @@
         ];
 
         // Check if this is a content taxonomy
-        if (!(JVB_TAXONOMY[$taxonomy]['is_content'] ?? false)) {
+		$registrar = Registrar::getInstance($taxonomy));
+		if (!$registrar->hasFeature('is_content')) {
             $results['messages'][] = "Taxonomy {$taxonomy} is not a content taxonomy";
             return $results;
         }
 
+		global $wpdb;
         $table = $wpdb->prefix . BASE . 'content_' . $taxonomy;
 
         // Check if table exists
diff --git a/inc/admin/Integrations.php b/inc/admin/Integrations.php
index cc11446..fea51c0 100644
--- a/inc/admin/Integrations.php
+++ b/inc/admin/Integrations.php
@@ -2,6 +2,7 @@
 namespace JVBase\admin;
 
 use JVBase\managers\IconsManager;
+use JVBase\registrar\Registrar;
 
 if (!defined('ABSPATH')) {
 	exit;
@@ -73,12 +74,12 @@
 
 		IconsManager::for()->enqueueIconStyles();
 		IconsManager::for('dash')->enqueueIconStyles();
-		IconsManager::for('form')->enqueueIconStyles();
+		IconsManager::for('forms')->enqueueIconStyles();
 
 		$queue = [
 			'api' => rest_url('jvb/v1/'),
 			'redirect' => wp_login_url(home_url(add_query_arg(null, null))),
-			'labels' => jvbGetLabels(),
+			'labels' => Registrar::getLabels(),
 		];
 
 		wp_localize_script('jvb-auth', 'jvbSettings', $queue);
diff --git a/inc/admin/SEOAdmin.php b/inc/admin/SEOAdmin.php
new file mode 100644
index 0000000..055f812
--- /dev/null
+++ b/inc/admin/SEOAdmin.php
@@ -0,0 +1,389 @@
+<?php
+namespace JVBase\admin;
+
+use JVBase\meta\Form;
+use JVBase\meta\Sanitizer;
+use JVBase\registrar\Registrar;
+use JVBase\ui\Tabs;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class SEOAdmin {
+	protected string $admin_page;
+	protected array $types = [
+		'JVBase\managers\SEO\render\Thing' 									=> 'Thing',
+		'JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork'		=> ' - Creative Work',
+		'JVBase\managers\SEO\render\Thing\CreativeWork\CategoryCodeSet'		=> '- - Category Code Set',
+		'JVBase\managers\SEO\render\Thing\CreativeWork\Certification'		=> '- - Certification',
+		'JVBase\managers\SEO\render\Thing\CreativeWork\Clip'				=> ' - - Clip',
+		'JVBase\managers\SEO\render\Thing\CreativeWork\DefinedTermSet'		=> ' - - Defined Term Set',
+		'JVBase\managers\SEO\render\Thing\CreativeWork\HowTo'				=> ' - - How To',
+		'JVBase\managers\SEO\render\Thing\CreativeWork\Menu'				=> ' - - Menu',
+		'JVBase\managers\SEO\render\Thing\CreativeWork\MenuSection'			=> ' - - Menu Section',
+		'JVBase\managers\SEO\render\Thing\CreativeWork\MusicRecording'		=> ' - - Music Recording',
+		'JVBase\managers\SEO\render\Thing\CreativeWork\Photograph'			=> ' - - Photograph',
+		'JVBase\managers\SEO\render\Thing\CreativeWork\Review'				=> ' - - Review',
+		'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\Webpage'		=> ' - - WebPage',
+		'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\AboutPage'	=> ' - - - About Page',
+		'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\CheckoutPage'	=> ' - - - Checkout Page',
+		'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\CollectionPage'	=> ' - - - Collection Page',
+		'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\ContactPage'		=> ' - - - Contact Page',
+		'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\FAQPage'			=> ' - - - FAQ Page',
+		'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\ItemPage'		=> ' - - - Item Page',
+		'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\MedicalWebPage'	=> ' - - - Medical Web Page',
+		'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\ProfilePage'		=> ' - - - Profile Page',
+		'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\QAPage'			=> ' - - - QA Page',
+		'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\RealEstateListing'	=> ' - - - Real Estate Listing',
+		'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\SearchResultsPage'	=> ' - - - Search Results Page',
+		'JVBase\managers\SEO\render\Thing\CreativeWork\WebSite'				=> ' - - - Website',
+		'JVBase\managers\SEO\render\Thing\Event\Event'						=> ' - Event',
+		'JVBase\managers\SEO\render\Thing\Organization\Organization'		=> ' - Organization',
+		'JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\LocalBusiness'					=> ' - - Local Business',
+		'JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment'				=> ' - - - Food Establishment',
+		'JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment\Bakery'				=> ' - - - - Bakery',
+		'JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment\BarOrPub'			=> ' - - - - Bar or Pub',
+		'JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment\Brewery'				=> ' - - - - Brewery',
+		'JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment\CafeOrCoffeeShop'	=> ' - - - - Cafe or Coffee Shop',
+		'JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment\Distillery'			=> ' - - - - Distillery',
+		'JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment\FastFoodRestaurant'	=> ' - - - - Fast Food Restaurant',
+		'JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment\IceCreamShop'		=> ' - - - - Ice Cream Shop',
+		'JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment\Restaurant'			=> ' - - - - Restaurant',
+		'JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment\Winery'				=> ' - - - - Winery',
+		'JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\HealthAndBeautyBusiness'		=> ' - - - Health & Beauty Business',
+		'JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\MedicalBusiness\MedicalBusiness'		=> ' - - - Medical Business',
+		'JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\ProfessionalService'			=> ' - - - Professional Service',
+		'JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\Store'							=> ' - - - Store',
+		'JVBase\managers\SEO\render\Thing\Person' 							=> ' - Person',
+		'JVBase\managers\SEO\render\Thing\Place' 							=> ' - Place',
+		'JVBase\managers\SEO\render\Thing\Product' 							=> ' - Product',
+	];
+
+	protected array $isHidden = [
+		'additionalType',
+		'mainEntityOfPage',
+		'subjectOf',
+		'brand',
+		'companyRegistration',
+		'contactPoint',
+		'department',
+		'duns',
+		'employee',
+		'ethicsPolicy',
+		'event',
+		'founder',
+		'funder',
+		'funding',
+		'globalLocationNumber',
+		'hasMerchantReturnPolicy',
+		'hasPOS',
+		'interactionStatistic',
+		'knowsLanguage',
+		'makesOffer',
+		'member',
+		'memberOf',
+		'ownershipFundingInfo',
+		'sponsor',
+		'containsInPlace',
+		'containsPlace',
+		'openingHours'
+	];
+
+	protected array $hints = [
+		'alternateName'				=> 'An alias for the item.',
+		'description'				=> 'A description of the item.',
+		'disambiguatingDescription'	=> 'A sub property of description. A short description of the item used to disambiguate from other, similar items. Information from other properties (in particular, name) may be necessary for the description to be useful for disambiguation.',
+		'image'						=> 'An image of the item.',
+		'name'						=> 'The name of the item.',
+		'owner'						=> 'A person or organization who owns this Thing.',
+		'slogan'					=> 'A slogan or motto associated with the item.'
+	];
+
+	protected array $checks;
+
+	public function __construct()
+	{
+		$this->setChecks();
+		add_filter('jvbDashboardPage', [$this, 'addDashboardSection'], 20, 2);
+		add_action('admin_menu', [$this, 'addAdminMenu']);
+		$this->addFormListeners();
+	}
+
+	protected function setChecks():void
+	{
+		$checks = [
+			'website'
+		];
+		$this->checks = array_merge($checks, Registrar::getRegistered());
+	}
+
+	protected function addFormListeners():void
+	{
+
+		foreach ($this->checks as $check){
+			$based = jvbCheckBase($check);
+			add_action('admin_post_'.$based, [$this, 'handleAJAXFormSubmit']);
+		}
+	}
+
+	public function addAdminMenu():void
+	{
+		$main = JVB()->admin()->getMainConfig();
+		$this->admin_page = add_submenu_page(
+			$main['menu_slug'],
+			'SEO',
+			'SEO',
+			'manage_options',
+			'jvb-seo',
+			[$this, 'renderAdminPage']
+		);
+	}
+
+	public function addDashboardSection(string $content, string $page):string
+	{
+		if ($page !== 'jvb-seo') {
+			return $content;
+		}
+		ob_start();
+		$this->renderAdminPage();
+		return ob_get_clean();
+	}
+
+	public function renderAdminPage(bool $outputScripts = true):void
+	{
+		?>
+		<div class="wrap jvb-seo-admin">
+			<h1><?= jvbDashIcon('magnifying-glass'); ?> SEO Configuration</h1>
+
+			<?php
+			$tabs = new Tabs();
+
+			$tabs->addTab('website')
+				->title('Website')
+				->icon('globe-simple')
+				->content($this->renderWebsite());
+
+			$tabs->addTab('business')
+				->title('Organization')
+				->icon('storefront')
+				->content($this->renderOrganization());
+
+//			if (count(Registrar::getRegistered('post')) > 0) {
+//				$tabs->addTab('content')
+//					->title('Content')
+//					->icon('note')
+//					->content($this->renderConfig('content'));
+//			}
+//
+//
+//			if (count(Registrar::getRegistered('taxonomy')) > 0) {
+//				$tabs->addTab('taxonomies')
+//					->title('Taxonomies')
+//					->icon('tag')
+//					->content($this->renderConfig('taxonomy'));
+//			}
+//
+//			if (count(Registrar::getRegistered('user')) > 0) {
+//				$tabs->addTab('user-roles')
+//					->title('User Roles')
+//					->icon('users-three')
+//					->content($this->renderConfig('taxonomy'));
+//			}
+
+			echo $tabs->render();
+			?>
+		</div>
+
+		<?php
+		if ($outputScripts) {
+			$this->renderStyles();
+		}
+	}
+
+	public function renderProperty(string $property, ?string $value, mixed $class):void
+	{
+		$method = 'get'.ucFirst($property).'FieldConfig';
+		$config = method_exists($class, $method) ? $class->$method() : $this->buildConfig($property);
+		if(!array_key_exists('type', $config)) {
+			error_log('Invalid Config for '.$property.': '.print_r($config, true));
+		}
+		echo Form::render($property, $value, $config);
+	}
+
+		protected function buildConfig($property) {
+			return [
+				'type'	=> $this->getPropertyType($property),
+				'label'	=> $this->getLabel($property)
+			];
+		}
+		protected function getPropertyType(string $property):string
+		{
+			return match($property) {
+				default => 'text'
+			};
+		}
+		protected function getLabel(string $property):string
+		{
+			$data = preg_split('/(?=[A-Z])/', $property);
+			$string = implode(' ', $data);
+			return ucwords($string);
+		}
+
+		public function renderSchemaTypeSelection(?string $value = null):void
+		{
+			$config = [
+				'type'		=> 'select',
+				'label'		=> 'Schema Type',
+				'hint'		=> 'Save changes & refresh after changing.',
+				'options'	=> $this->types
+			];
+			echo Form::render('type', $value, $config);
+		}
+
+	public function renderWebsite():string
+	{
+		ob_start();
+		$this->formStart('website');
+		$default = 'JVBase\managers\SEO\render\Thing\CreativeWork\WebSite';
+//		$this->renderSchemaTypeSelection($value);
+		$stored = get_option(BASE.'WebsiteSchema',apply_filters(BASE.'websiteSchema', []));
+		$this->renderFieldsFor($default, $stored);
+		$this->formEnd();
+		return ob_get_clean();
+	}
+	public function renderOrganization():string
+	{
+		ob_start();
+		$this->formStart('organization');
+		$default = apply_filters(BASE.'OrganizationSchemaType', 'JVBase\managers\SEO\render\Thing\Organization\Organization');
+		$value = get_option(BASE.'OrganizationSchemaType', $default);
+		$this->renderSchemaTypeSelection($value);
+		$stored = get_option(BASE.'OrganizationSchema',apply_filters(BASE.'OrganizationSchema', []));
+		$this->renderFieldsFor($value, $stored);
+		$this->formEnd();
+		return ob_get_clean();
+	}
+
+	protected function formStart(string $action):void
+	{
+		$action = jvbCheckBase($action);
+
+		echo sprintf(
+			'<form action="%s" method="POST">
+			<input type="hidden" name="action" value="%s">
+			<input type="hidden" name="%s_nonce" value="%s">',
+			esc_url(admin_url('admin-post.php')),
+			$action,
+			$action,
+			wp_create_nonce($action)
+		);
+	}
+	protected function formEnd():void
+	{
+		echo '<button type="submit">Save Changes</button>
+			</form>';
+	}
+	public function renderFieldsFor(string $class, array $stored):void
+	{
+		$fields = $this->getFieldsFor($class);
+		$instance = new $class();
+		foreach ($fields as $property => $value) {
+			$this->renderProperty($property, $stored[$property]??null, $instance);
+		}
+	}
+
+	public function getFieldsFor(string $class):array
+	{
+		if (!class_exists($class)) {
+			error_log('Class not found: '.$class);
+			return [];
+		}
+		$class = new $class();
+
+		return array_filter($class->getProperties(), function ($item) {
+			return !in_array($item, $this->isHidden);
+		}, ARRAY_FILTER_USE_KEY);
+	}
+
+	public function handleAJAXFormSubmit() {
+		$action = $_POST['action'];
+		if (!isset($_POST[$action.'_nonce']) || !wp_verify_nonce($_POST[$action.'_nonce'], $action)) {
+			wp_die( __( 'Invalid nonce specified', 'jvb' ), __( 'Error', 'jvb' ), array(
+				'response' 	=> 403,
+				'back_link' => 'admin.php?page=jvb-seo',
+			) );
+		}
+		$action = jvbNoBase($action);
+
+		error_log('[SEOAdmin]Post request: '.print_r($_POST, true));
+		if ($action !== 'website'){
+
+			$type = sanitize_text_field(stripslashes($_POST['type']));
+			if (empty($type) || !array_key_exists($type, $this->types)){
+				error_log('Invalid type: '.print_r($type, true));
+				return;
+			}
+			update_option(BASE.ucfirst($action).'SchemaType', $type);
+		} else {
+			$type = 'JVBase\managers\SEO\render\Thing\CreativeWork\WebSite';
+		}
+
+
+
+		$result = $this->saveFields($action, $type, $_POST);
+
+
+		// redirect the user to the appropriate page
+		wp_redirect(esc_url_raw(add_query_arg($result,
+			admin_url('admin.php?page=jvb-seo')
+		)));
+		exit;
+	}
+
+	public function saveFields(string $action, string $class, array $data):array
+	{
+		if (!in_array($action, $this->checks)) {
+			error_log('[SEOAdmin]Action is not allowed: '.$action);
+			return [
+				'jvb_notice'	=> 'error',
+				'jvb_message'	=> 'Action is not allowed: '.$action
+			];
+		}
+
+		$allowed = $this->getFieldsFor($class);
+		if (empty($allowed)) {
+			return [
+				'jvb_notice'	=> 'error',
+				'jvb_message'	=> 'Could not get fields from class'
+			];
+		}
+
+		$checked = array_filter($data, function ($item) use ($allowed) {
+			return array_key_exists($item, $allowed);
+		}, ARRAY_FILTER_USE_KEY);
+
+		$stored = get_option(BASE.ucfirst($action).'Schema', []);
+		$updates = [];
+		foreach ($checked as $property => $value) {
+			$sanitized = Sanitizer::sanitize($value, $this->buildConfig($property));
+			if (!array_key_exists($property, $stored) || $stored[$property] !== $sanitized)
+			$updates[$property] = $sanitized;
+		}
+		if (!empty($updates)) {
+			$history = get_option(BASE.ucfirst($action).'SchemaHistory', []);
+			array_unshift($history, $stored);
+			if (count($history) > 5){
+				array_pop($history);
+			}
+			update_option(BASE.ucfirst($action).'SchemaHistory', $history);
+
+			$update = array_merge($stored, $updates);
+			update_option(BASE.ucfirst($action).'Schema', $update);
+		}
+		return [
+			'jvb_notice'	=> 'success',
+			'jvb_message'	=> 'Saved changes successfully'
+		];
+	}
+}
diff --git a/inc/admin/_setup.php b/inc/admin/_setup.php
index f1ceef6..da84e83 100644
--- a/inc/admin/_setup.php
+++ b/inc/admin/_setup.php
@@ -1,8 +1,10 @@
 <?php
-use JVBase\utility\Features;
+
+use JVBase\registrar\Registrar;
 
 require(JVB_DIR . '/inc/admin/Integrations.php');
+require(JVB_DIR . '/inc/admin/SEOAdmin.php');
 
-if (Features::anyTaxonomyHas('is_content')) {
+if (!empty(Registrar::getFeatured('is_content', 'term'))) {
 	require(JVB_DIR . '/inc/admin/ContentTaxonomy.php');
 }
diff --git a/inc/blocks/CustomBlocks.php b/inc/blocks/CustomBlocks.php
index b22cb0a..93e16ac 100644
--- a/inc/blocks/CustomBlocks.php
+++ b/inc/blocks/CustomBlocks.php
@@ -633,14 +633,14 @@
 		global $post;
 		$ID = get_post_thumbnail_id($post->ID);
 		$aOpen = $aClose = '';
-		if(!is_single($ID)) {
+		if(!is_single($post->ID)) {
 			$aOpen = '<a href="'.get_the_permalink($post->ID).'">';
 			$aClose = '</a>';
 		}
 
-        return $aOpen.'<figure'.$this->getClassesAndStyles($block['attrs']).'>'.
+        return '<figure'.$this->getClassesAndStyles($block['attrs']).'>'.$aOpen.
                apply_filters('jvbCoreFeaturedImage', $this->image($ID), $post->post_type).
-               '</figure>'.$aClose;
+				$aClose.'</figure>';
     }
     //core_post_navigation_link
     //core_post_template
diff --git a/inc/blocks/FeedBlock.php b/inc/blocks/FeedBlock.php
index e100409..9bcf1cf 100644
--- a/inc/blocks/FeedBlock.php
+++ b/inc/blocks/FeedBlock.php
@@ -2,8 +2,8 @@
 namespace JVBase\blocks;
 
 use JVBase\managers\Cache;
+use JVBase\registrar\Registrar;
 use JVBase\utility\Features;
-use JVBase\utility\Checker;
 use JVBase\forms\TaxonomySelector;
 use WP_Block;
 
@@ -53,12 +53,13 @@
 
 		if (is_post_type_archive() || is_singular()) {
 			$content = is_singular() ? jvbNoBase($type->post_type) : jvbNoBase($type->name);
-			$mainConfig = JVB_CONTENT[$content]??false;
-			if ($mainConfig && array_key_exists('feed', $mainConfig) && array_key_exists('config', $mainConfig['feed'])){
-				$config = array_merge($config, $mainConfig['feed']['config']);
+
+			$registrar = Registrar::getInstance($content)??false;
+			if ($registrar) {
+				$config = array_merge($config, $registrar->getConfig('feed'));
 			} else {
 				$config['content'] = $content;
-				$config['icon'] = JVB_CONTENT[$content]['icon']??[jvbLogoIcon()];
+				$config['icon'] = jvbDefaultIcon();
 			}
 			if (is_singular()) {
 				$config['source'] = $type->ID;
@@ -67,13 +68,13 @@
 			$config['taxonomies'] = $this->getTaxonomies([$content]);
 		} elseif (is_tax()) {
 			$content = jvbNoBase($type->taxonomy);
-			$mainConfig = JVB_TAXONOMY[$content]??false;
-			if ($mainConfig) {
-				$config['content'] = $mainConfig['for_content'];
-				$config['context'] = $content;  // ← ADD THIS
-				$config['taxonomies'] = $this->getTaxonomies($mainConfig['for_content']);
-				if (array_key_exists('feed', $mainConfig) && array_key_exists('config', $mainConfig['feed'])){
-					$config = array_merge($config, $mainConfig['feed']['config']);
+			$registrar = Registrar::getInstance($content)??false;
+			if ($registrar) {
+				$config['content'] = $registrar->registrar->for;
+				$config['context'] = $content;
+				$config['taxonomies'] = $this->getTaxonomies($registrar->registrar->for);
+				if (!empty($registrar->getConfig('feed'))){
+					$config = array_merge($config, $registrar->getConfig('feed'));
 				}
 			}
 			$config['source'] = $type->term_id;
@@ -88,17 +89,19 @@
 
 	/**
 	 * Get taxonomies for given content types
-	 * Uses Checker instead of globals
+
 	 */
 	protected function getTaxonomies(array $content): array
 	{
-		$checker = Checker::getInstance();
+
 		$taxonomies = [];
 
 		foreach ($content as $contentType) {
-			$contentTaxonomies = $checker->getTaxonomiesForContent($contentType);
+			$registrar = Registrar::getInstance($contentType);
+
+			$contentTaxonomies = $registrar->registrar->taxonomies;
 			$contentTaxonomies = array_filter($contentTaxonomies, function($taxonomy) {
-				return array_key_exists('show_feed', JVB_TAXONOMY[$taxonomy]) && JVB_TAXONOMY[$taxonomy]['show_feed'];
+				return Registrar::getInstance($taxonomy)->hasFeature('show_feed');
 			});
 			$taxonomies = array_merge($taxonomies, $contentTaxonomies);
 		}
@@ -248,19 +251,18 @@
 							<span class="label">FILTER BY:</span>
 
 							<?php
-							$checker = Checker::getInstance();
 							foreach ($this->config['taxonomies'] as $tax) :
-								$taxConfig = JVB_TAXONOMY[$tax] ?? null;
-								if (!$taxConfig) continue;
+								$registrar = Registrar::getInstance($tax)??false;
+								if (!$registrar) continue;
 
-								$contentForTax = $checker->getContentForTaxonomy($tax);
+								$contentForTax = $registrar->registrar->for;
 								$hidden = empty($contentForTax) ? ' hidden' : '';
 
 								$taxSelector = new TaxonomySelector(
 									'feed-'.$tax,
 									$tax,
 									[
-										'icon'			=> $taxConfig['icon']??jvbLogoIcon(),
+										'icon'			=> $registrar->getIcon()??jvbLogoIcon(),
 										'update' 		=> '.selected-items-section .selected-items',
 										'types' 		=> $contentForTax,
 										'autocomplete'	=> false,
@@ -311,9 +313,10 @@
 							<?php
 								$custom = [];
 								foreach ($this->getContent() as $content) {
-									$config = JVB_CONTENT[$content]??JVB_TAXONOMY[$content]??JVB_USER[$content]??false;
-									if ($config && array_key_exists('custom_order', $config)) {
-										$custom = array_merge_recursive($custom, $config['custom_order']);
+									$registrar = Registrar::getInstance($content)??false;
+
+									if ($registrar && !empty($registrar->config('feed')->getCustomOrder())) {
+										$custom = array_merge_recursive($custom, $registrar->config('feed')->getCustomOrder());
 									}
 								}
 								foreach ($custom as $slug => $conf) {
@@ -365,8 +368,8 @@
 			$total = count($this->getContent()) - 1;
 			for ($i = 1; $i <= 36; $i++) {
 				$rand = rand(0, $total);
-				$config = Features::getConfig($this->getContent()[$rand]);
-				$icon = jvbIcon($config['icon']??jvbLogoIcon());
+				$config = Registrar::getInstance($this->getContent()[$rand]);
+				$icon = jvbIcon($config->getIcon??jvbLogoIcon());
 				?>
 				<div class="placeholder"><?=apply_filters('jvbFeedPlaceholder', $icon) ?></div>
 				<?php
@@ -410,7 +413,8 @@
 
 	protected function getFavouritesButton(string $content):string
 	{
-		if (!Features::forSite()->has('favourites') && !Features::forContent($content)->has('favouritable')) {
+		$registrar = Registrar::getInstance($content);
+		if (!$registrar || !Features::forSite()->has('favourites') || !$registrar->hasFeature('favouritable')) {
 			return '';
 		}
 		return '<button class="favourite" type="button" title="Add to favourites" data-action="favourite">
@@ -420,7 +424,8 @@
 	}
 	protected function getUpvotesButton(string $content):string
 	{
-		if (!Features::forSite()->has('karma') && !Features::forContent($content)->has('karma')){
+		$registrar = Registrar::getInstance($content);
+		if (!Features::forSite()->has('karma') || !$registrar || !$registrar->hasFeature('karma')){
 			return '';
 		}
 		return '<div class="karma row">
@@ -437,14 +442,16 @@
 	}
 	protected function getDefaultTemplate(string $content): string
 	{
-		$config = JVB_CONTENT[$content]??[];
-		$hasConfig = array_key_exists('feed', $config);
-		$images = ($hasConfig && array_key_exists('images', $config['feed']) ? $config['feed']['images']:['post_thumbnail']);
-		$images = (is_array($images)) ? $images : [$images];
-		$fields = ($hasConfig && array_key_exists('fields', $config['feed']) ? $config['feed']['fields']:['post_title', 'post_date']);
+		$config = Registrar::getInstance($content)->getConfig('feed');
+		$allFields = Registrar::getFieldsFor($content);
+		$images = $config['images']??['post_thumbnail'];
+		$fields = $config['fields']??['post_title','post_date','post_excerpt'];
 		$fields = array_filter($fields, function($field) use($images) {
 			return !in_array($field, $images);
 		});
+		$fields = array_filter($allFields, function($field) use($fields) {
+			return in_array($field, $fields);
+		}, ARRAY_FILTER_USE_KEY);
 		$template = '<div class="feed item col '.$content.'">'.$this->getFavouritesButton($content).$this->getUpvotesButton($content);
 
 		//Add all defined images, but allow for filtering
@@ -461,9 +468,8 @@
 
 		$fieldsTemplate = '';
 
-		foreach ($fields as $fieldName) {
-			$fieldType = JVB_CONTENT[$content][$fieldName]['type']??'text';
-			$fieldsTemplate .= apply_filters('jvbFeedItemField', $this->defaultFieldTemplate($fieldType, $fieldName), $content, $fieldName, $fieldType);
+		foreach ($fields as $fieldName => $config) {
+			$fieldsTemplate .= apply_filters('jvbFeedItemField', $this->defaultFieldTemplate($config['type'], $fieldName), $content, $fieldName, $config['type']);
 		}
 		$template .= '<div class="item-info">'.apply_filters('jvbFeedItemFields', $fieldsTemplate, $content, $fields).'</div>';
 		$template .= '</details></div>';
diff --git a/inc/blocks/RegisterBlocks.php b/inc/blocks/RegisterBlocks.php
index 5408503..f4360f3 100644
--- a/inc/blocks/RegisterBlocks.php
+++ b/inc/blocks/RegisterBlocks.php
@@ -1,5 +1,6 @@
 <?php
 
+use JVBase\registrar\Registrar;
 use JVBase\utility\Features;
 
 if (!defined('ABSPATH')) {
@@ -13,11 +14,11 @@
 require(JVB_DIR . '/build/summary/render.php');
 require(JVB_DIR . '/build/forms/render.php');
 require(JVB_DIR . '/build/menu/render.php');
-if (Features::anyContentHas('is_glossary')) {
+if (!empty(Registrar::getFeatured('is_glossary'))) {
 	error_log('Has Glossary Type');
 	require(JVB_DIR . '/build/glossary/render.php');
 }
-if (Features::forSite()->has('faq') || array_key_exists('faq', JVB_CONTENT)) {
+if (Features::forSite()->has('faq')) {
 	require(JVB_DIR . '/build/faq/render.php');
 }
 if (Features::hasIntegration('gmb')) {
@@ -38,7 +39,7 @@
 //            ]
 //        );
 //    }
-	if (Features::anyContentHas('show_directory') || Features::anyTaxonomyHas('show_directory')) {
+	if (!empty(Registrar::getFeatured('show_directory'))) {
         register_block_type(
             JVB_DIR . '/build/list',
             [
@@ -60,22 +61,3 @@
     ]);
 }
 add_filter('block_categories_all', 'jvbRegisterBlockCategory');
-
-
-/**
- * Enqueue block editor assets
- */
-function jvbEnqueueBlockEditorAssets():void
-{
-    // Only localize for block editor
-    if (!is_admin()) {
-        return;
-    }
-    // Localize list types for the block
-    wp_localize_script(
-        'jvb-list-editor-script', // This should match your editor script handle
-        'jvbListTypes',
-        jvbGlobalDirectoryInfo()
-    );
-}
-add_action('enqueue_block_editor_assets', 'jvbEnqueueBlockEditorAssets');
diff --git a/inc/blocks/TimelineBlock.php b/inc/blocks/TimelineBlock.php
index 09d1b14..e2831d4 100644
--- a/inc/blocks/TimelineBlock.php
+++ b/inc/blocks/TimelineBlock.php
@@ -3,6 +3,7 @@
 
 use JVBase\managers\Cache;
 use JVBase\meta\Meta;
+use JVBase\registrar\Registrar;
 use JVBase\utility\Features;
 use WP_Block;
 
@@ -47,7 +48,8 @@
     public function render(array $attributes, string $content, WP_Block $block)
     {
 		global $post;
-		if (!$post || !Features::forContent(jvbNoBase($post->post_type))->has('is_timeline') ) {
+		$registrar = Registrar::getInstance($post->post_type));
+		if (!$post || !$registrar || !$registrar->hasFeature('is_timeline') ) {
 			return '';
 		}
 		$this->parentID = $post->ID;
@@ -100,15 +102,15 @@
 					if ($value === '' || $slug === 'person') {
 						continue;
 					}
-					$config = JVB_TAXONOMY[$slug];
+					$registrar = Registrar::getInstance($slug);
 					$taxSlug = jvbCheckBase($slug);
 					$terms = get_the_terms($this->parentID, $taxSlug);
 					if ($terms && !is_wp_error($terms)) {
 						$many = count($terms) > 1;
 					?>
 					<li class="<?=$slug?>">
-						<span title="<?= $config['singular']?>" class="term tldr" data-short="" data-long="<?= $config['singular']?>">
-							<?=jvbIcon($config['icon']??jvbDefaultIcon())?>
+						<span title="<?= $registrar->getSingular()?>" class="term tldr" data-short="" data-long="<?= $registrar->getSingular()?>">
+							<?=jvbIcon($registrar->getIcon())?>
 							<span></span>
 						</span>
 						<?php
@@ -219,9 +221,10 @@
 			return '';
 		}
 		$out = '<ul class="term-list">';
+		$registrar = Registrar::getInstance('timeline');
 		foreach ($timeline as $term) {
 			$link = get_term_link($term->term_id, BASE.'timeline');
-			$out .= '<li><a href="'.$link.'" rel="tag" title="See more progressions at this timeline">'.jvbIcon(JVB_TAXONOMY['timeline']['icon']??'hourglass').html_entity_decode($term->name).'</a><small>after the treatment</small></li>';
+			$out .= '<li><a href="'.$link.'" rel="tag" title="See more progressions at this timeline">'.jvbIcon($registrar->getIcon('hourglass')).html_entity_decode($term->name).'</a><small>after the treatment</small></li>';
 		}
 		$out .='</ul>';
 		return $out;
diff --git a/inc/blocks/_setup.php b/inc/blocks/_setup.php
index c6955cf..a2ad8e4 100644
--- a/inc/blocks/_setup.php
+++ b/inc/blocks/_setup.php
@@ -1,4 +1,6 @@
 <?php
+
+use JVBase\registrar\Registrar;
 use JVBase\utility\Features;
 
 //require(JVB_DIR . '/inc/blocks/RegisterBlocks.php');
@@ -14,17 +16,17 @@
 	new JVBase\blocks\MenuBlock();
 }
 
-if (Features::forSite()->has('faq') || array_key_exists('faq', JVB_CONTENT)) {
+if (Features::forSite()->has('faq')) {
 	require(JVB_DIR . '/inc/blocks/FAQBlock.php');
 	new JVBase\blocks\FAQBlock();
 }
 
 
-if (Features::anyContentHas('is_gallery')) {
+if (!empty(Registrar::getFeatured('is_gallery'))) {
 	require(JVB_DIR . '/inc/blocks/GlossaryBlock.php');
 	new JVBase\blocks\GlossaryBlock();
 }
-if (Features::anyContentHas('is_timeline')) {
+if (!empty(Registrar::getFeatured('is_timeline'))) {
 	require(JVB_DIR . '/inc/blocks/TimelineBlock.php');
 	new JVBase\blocks\TimelineBlock();
 }
@@ -70,7 +72,7 @@
 //            ]
 //        );
 //    }
-	if (Features::anyContentHas('show_directory') || Features::anyTaxonomyHas('show_directory')) {
+	if (!empty(Registrar::getFeatured('show_directory'))) {
 		register_block_type(
 			JVB_DIR . '/build/list',
 			[
diff --git a/inc/forms/TaxonomySelector.php b/inc/forms/TaxonomySelector.php
index bcc5a3b..5af59da 100644
--- a/inc/forms/TaxonomySelector.php
+++ b/inc/forms/TaxonomySelector.php
@@ -1,6 +1,7 @@
 <?php
 namespace JVBase\forms;
 
+use JVBase\registrar\Registrar;
 use WP_Term;
 
 if (!defined('ABSPATH')) {
@@ -27,13 +28,19 @@
 	protected string $base;
 	protected string $title;
 	protected array $config;
+	protected Registrar $registrar;
 
 	public function __construct(string $id, string $taxonomy, array $config = []) {
 		$this->id = sanitize_key($id);
 		$this->taxonomy = jvbCheckBase($taxonomy);
 		$this->name = jvbNoBase($taxonomy);
 
-		$this->title = JVB_TAXONOMY[$this->name]['plural'];
+		$registrar = Registrar::getInstance($this->name);
+		if ($registrar) {
+			$this->registrar = $registrar;
+		}
+
+		$this->title = $registrar->getPlural();
 		$this->base = $config['base']??'';
 		$this->config = wp_parse_args($config, [
 			'types'		=> false, // for feed block implementation
@@ -50,8 +57,8 @@
 			'update'	=> true,   // Whether to update on close
 		]);
 
-		$this->plural = JVB_TAXONOMY[$taxonomy]['plural'];
-		$this->singular = JVB_TAXONOMY[$taxonomy]['singular'];
+		$this->plural = $registrar->getPlural();
+		$this->singular = $registrar->getSingular();
 	}
 
 
@@ -272,12 +279,12 @@
 	{
 		return sprintf(
 			'<button type="button" data-icon="%s" data-filter="taxonomy" data-taxonomy="%s" data-type="selector" data-single="%s" data-plural="%s" title="Filter by %s">%s<span class="label">%s</span></button>',
-			JVB_TAXONOMY[$this->name]['icon'],
+			$this->registrar->getIcon(),
 			$this->name,
 			$this->singular,
 			$this->plural,
 			$this->singular,
-			jvbIcon($this->config['icon']),
+			jvbIcon($this->registrar->getIcon()),
 			$this->singular
 		);
 	}
diff --git a/inc/helpers/all.php b/inc/helpers/all.php
index 3931fc3..5b7f02b 100644
--- a/inc/helpers/all.php
+++ b/inc/helpers/all.php
@@ -19,13 +19,13 @@
 require(JVB_DIR . '/inc/helpers/media.php');
 require(JVB_DIR . '/inc/helpers/members.php');
 require(JVB_DIR . '/inc/helpers/renderFields.php');
-require(JVB_DIR . '/inc/helpers/saveFields.php');
+//require(JVB_DIR . '/inc/helpers/saveFields.php');
 require(JVB_DIR . '/inc/helpers/terms.php');
 require(JVB_DIR . '/inc/helpers/time.php');
 require(JVB_DIR . '/inc/helpers/ui.php');
 //require(JVB_DIR . '/inc/helpers/umami.php');
 
-add_action('init', 'jvb_do_once');
+add_action('wp_loaded', 'jvb_do_once',99);
 /**
  * Helper to do things once
  * @return void
@@ -35,7 +35,6 @@
 
 //    delete_option(BASE.'do_these_once');
     $options = get_option(BASE.'do_these_once', []);
-
     foreach ($options as $option => $callback) {
 //        delete_option($option);
         if (!get_option($option, false)) {
@@ -46,11 +45,11 @@
     }
 }
 
-function jvb_register_do_once(string $option, array $callback)
+function jvb_register_do_once(string $option, array|string $callback):void
 {
 //    delete_option(BASE.'do_these_once');
     //Ensure we have the option starting with BASE
-    $option = (str_starts_with($option, BASE)) ? $option : BASE.$option;
+    $option = jvbCheckBase($option);
     $options = get_option(BASE.'do_these_once', []);
 //    delete_option($option);
     if (!array_key_exists($option, $options)) {
diff --git a/inc/helpers/crud.php b/inc/helpers/crud.php
index e213573..6245bff 100644
--- a/inc/helpers/crud.php
+++ b/inc/helpers/crud.php
@@ -6,6 +6,7 @@
 
 use JVBase\managers\Cache;
 use JVBase\meta\Form;
+use JVBase\registrar\Registrar;
 
 /**
  * For whatever reason, after much testing, it seems that
@@ -57,6 +58,7 @@
 }
 
 /**
+ * @deprecated use CRUDManager.php or CRUDSkeleton.php
  * Outputs the blocks of a CRUD management in backend
  * Mainly used in news.php so far
  * @param string $content
@@ -346,7 +348,7 @@
     $nav .= '</nav>';
     echo $nav;
 
-    $fields = jvbGetFields($postType);
+    $fields = Registrar::getFieldsFor($postType);
     ?>
     <form class="jvb-form" id="bio" data-form-id="bio-<?=$ID?>" data-save="bio"
           data-object-id="<?=$ID?>" data-content-type="<?=$postType?>">
diff --git a/inc/helpers/dashboard.php b/inc/helpers/dashboard.php
index e9a32b4..06f574b 100644
--- a/inc/helpers/dashboard.php
+++ b/inc/helpers/dashboard.php
@@ -1,5 +1,7 @@
 <?php
 
+use JVBase\registrar\Registrar;
+
 if (!defined('ABSPATH')) {
 	exit;
 }
@@ -60,91 +62,12 @@
             array_map(function ($role) {
                 return BASE.$role;
             },
-               jvbRolesWithDashboard())
+               Registrar::getFeatured('has_dashboard', 'user'))
         )
     )>0;
 }
 
-/**
- * The basis for the dashboard navigation
- * @return array
- */
-function jvbGetUserDashboardPages():array
-{
-    if (!isOurPeople() && !current_user_can('manage_options')) {
-        return [];
-    }
-    $user = wp_get_current_user();
-    $pages = get_transient('jvb_'.$user->ID.'_dashboard_pages');
-	$pages = false;
-    if (!$pages) {
-        $pages = ['dash'];
-        $roles = array_intersect(
-            jvbRolesWithDashboard(),
-            array_map(function ($role) {
-                return jvbNoBase($role);
-            },
-            $user->roles)
-        );
 
-        $content = [];
-
-        foreach ($roles as $role) {
-            $content = array_unique(array_merge(jvbRolePages($role), $content));
-        }
-		error_log('Content: '.print_r($content, true));
-        foreach ($content as $c) {
-
-			$permission = JVB_CONTENT[$c]['plural']??$c.'s';
-            if (current_user_can('edit_'.$permission)) {
-                $pages[] = $c;
-            }
-        }
-
-
-        if (jvbCheck('can_invite', JVB_MEMBERSHIP) && jvbUserIsVerified()) {
-            $pages[] = 'invites';
-        }
-        if (jvbCheck('term_approval', JVB_MEMBERSHIP) && jvbUserIsVerified()) {
-            $pages[] = 'approvals';
-        }
-        if (jvbSiteHasTermContent()) {
-            $terms = array_filter(JVB_TAXONOMY, function ($tax) {
-                return jvbCheck('is_content', $tax);
-            });
-            foreach ($terms as $term => $config) {
-                if (current_user_can('manage_'.$term)) {
-                    $pages[] = $config['plural'];
-                }
-            }
-        }
-
-		if (jvbSiteHasIntegrations()) {
-			//TODO: Check that user has integrations enabled
-			$pages[] = 'integrations';
-		}
-        if (jvbSiteHasForum() &&
-            (empty(JVB_MEMBERSHIP['member_only']??[]) || array_intersect(JVB_MEMBERSHIP['member_only'], $roles) > 0)) {
-            $pages[] = 'news';
-        }
-
-        if (jvbCheck('member_content', JVB_MEMBERSHIP) && jvbUserCanCreate()) {
-            $pages[] = 'metrics';
-        }
-
-        if (jvbCheck('favourites', JVB_SITE)) {
-            $pages[] = 'favourites';
-        }
-
-        if (jvbSiteHasSupport()) {
-            $pages[] = 'support';
-        }
-		$pages = apply_filters('jvbUserDashboardPages', $pages, $user->roles);
-        set_transient('jvb_'.$user->ID.'_dashboard_pages', $pages, WEEK_IN_SECONDS);
-    }
-
-    return $pages;
-}
 
 
 /**
@@ -167,25 +90,28 @@
         }
 
         $role = jvbUserRole((int) $link);
-        $config = JVB_USER[$role];
-        foreach ($config['can_create'] as $type) {
-            if (is_array($type)) {
-                $types = array_unique(array_merge($types, array_values($type)[0]));
-            } else {
-                $types[] = $type;
-            }
-        }
+		$registrar = Registrar::getInstance($role);
+        if ($registrar && !empty($registrar->getCreatable())){
+			foreach ($registrar->getCreatable() as $type) {
+				if (is_array($type)) {
+					$types = array_unique(array_merge($types, array_values($type)[0]));
+				} else {
+					$types[] = $type;
+				}
+			}
+		}
+
         $temp = [];
         foreach ($types as $t) {
-			$permission = JVB_CONTENT[$t]['plural']??$t.'s';
-            if (user_can($link, 'edit_'.$permission)){
+			$permission = JVB()->roles()->getPermission('edit',$t);
+            if (user_can($link, $permission)){
                 $temp[] = $t;
             }
         }
 
-        global $jvb_feed;
-        $types = array_filter($temp, function ($type) use ($jvb_feed) {
-            return array_key_exists($type, $jvb_feed);
+        $types = Registrar::getFeatured('show_feed');
+        $types = array_filter($temp, function ($type) use ($types) {
+            return in_array($type, $types);
         });
 
         update_post_meta($postID, BASE.'content_types', $types);
diff --git a/inc/helpers/saveFields.php b/inc/helpers/saveFields.php
deleted file mode 100644
index bf03102..0000000
--- a/inc/helpers/saveFields.php
+++ /dev/null
@@ -1,20 +0,0 @@
-<?php
-
-if (!defined('ABSPATH')) {
-	exit;
-}
-
-
-function jvbNoSaveIt(int $postID, \WP_Post $post): bool {
-	if (!$postID || $postID === 0) {
-		return true;
-	}
-	$postType = jvbNoBase($post->post_type);
-	if (!array_key_exists($postType, JVB_CONTENT)){
-		return true;
-	}
-	if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return true;
-	if (wp_is_post_revision($postID)) return true;
-
-	return false;
-}
diff --git a/inc/helpers/terms.php b/inc/helpers/terms.php
index 77f8fec..bf1c00c 100644
--- a/inc/helpers/terms.php
+++ b/inc/helpers/terms.php
@@ -42,69 +42,6 @@
 }
 
 /**
- * Clear taxonomy list caches when terms are saved
- * @param int $term_id
- * @param int $tt_id
- * @param string $taxonomy
- *
- * @return void
- */
-add_action('edited_term', 'jvbClearTermListCache', 10, 3);
-add_action('created_term', 'jvbClearTermListCache', 10, 3);
-add_action('delete_term', 'jvbClearTermListCache', 10, 3);
-
-function jvbClearTermListCache(int $term_id, int $tt_id, string $taxonomy):void
-{
-    // Check if this taxonomy has a corresponding list
-    $types = jvbGlobalDirectoryInfo();
-    foreach ($types as $type) {
-        if ($type['slug'] === $taxonomy) {
-            // Delete the option to force regeneration
-            delete_option(BASE . $type['slug'] . '_list');
-            break;
-        }
-    }
-
-    // If this is a city and it's linked to other taxonomies, clear those too
-    if ($taxonomy === BASE.'city') {
-        foreach ($types as $type) {
-            if (!empty($type['links']) && in_array(BASE.'_city', $type['links'])) {
-                delete_option(BASE . $type['slug'] . '_list');
-            }
-        }
-    }
-}
-
-/**
- * Clear list caches when post types are saved
- */
-add_action('save_post', 'jvbClearListCache', 10, 2);
-function jvbClearListCache(int $post_id, \WP_Post $post):void
-{
-	// SAFETY: Skip attachments and other non-content post types
-	if (in_array($post->post_type, jvbIgnoredPostTypes())) {
-		return;
-	}
-	if (jvbNoSaveIt($post_id, $post)) {
-		return;
-	}
-
-    // Get post type
-    $post_type = get_post_type($post_id);
-
-    // Check if this post type has a corresponding list
-    $types = jvbGlobalDirectoryInfo();
-    foreach ($types as $type) {
-        if ($type['slug'] === $post_type) {
-            // Delete the option to force regeneration
-            delete_option(BASE . $type['slug'] . '_list');
-            break;
-        }
-    }
-}
-
-
-/**
  * @param int $artistID
  * @param string $taxonomy
  *
diff --git a/inc/helpers/ui.php b/inc/helpers/ui.php
index 7a0b191..a491e36 100644
--- a/inc/helpers/ui.php
+++ b/inc/helpers/ui.php
@@ -111,7 +111,7 @@
  */
 function jvbNotificationMenu():string
 {
-    if (jvbSiteHasNotifications() && is_user_logged_in()) {
+    if (Features::forMembership()->has('notifications') && is_user_logged_in()) {
 
         ob_start();
         ?>
diff --git a/inc/integrations/Helcim.php b/inc/integrations/Helcim.php
index d9e4e97..3dd1400 100644
--- a/inc/integrations/Helcim.php
+++ b/inc/integrations/Helcim.php
@@ -170,11 +170,18 @@
 		}
 	}
 
+	public function getAdditionalFields(?string $content_type = null):array
+	{
+		return array_combine(
+			array_map(fn($k) => 'hc_' . $k, array_keys($this->getHelcimMeta($content_type))),
+			$this->getHelcimMeta($content_type)
+		);
+	}
+
 	/**
 	 * Get Helcim product meta fields by type.
 	 *
-	 * Used by FieldRegistry when 'use_helcim' => true is set
-	 * in a JVB_CONTENT definition.
+	 * Used by Registrar.php  when helcim is configured
 	 */
 	public function getHelcimMeta(?string $type = null): array
 	{
diff --git a/inc/integrations/Integrations.php b/inc/integrations/Integrations.php
index a89e56c..54f4cf4 100644
--- a/inc/integrations/Integrations.php
+++ b/inc/integrations/Integrations.php
@@ -6,6 +6,8 @@
 use JVBase\meta\Form;
 use JVBase\meta\Meta;
 use JVBase\managers\ErrorHandler;
+use JVBase\registrar\helpers\AddIntegrationFields;
+use JVBase\registrar\Registrar;
 use WP_Error;
 use WP_Post;
 use WP_REST_Request;
@@ -91,6 +93,7 @@
 		'test_connection' => 'Test Connection',
 	];
 
+	protected array $allowedContent = [];
 
 	/**
 	 * Caching Configuration
@@ -131,8 +134,8 @@
 		'update' => false,     // Can update already-shared posts
 		'delete' => false,     // Can remove posts from the service
 	];
-	protected array $syncPostTypes = []; // Post types that can be synced (e.g., ['artwork', 'tattoo']): usually built by querying the global JVB_CONTENT if the integration name exists as a key in [ 'integrations' => []]
-	protected array $syncTaxonomies = []; // Post types that can be synced (e.g., ['artwork', 'tattoo']): usually built by querying the global JVB_CONTENT if the integration name exists as a key in [ 'integrations' => []]
+	protected array $syncPostTypes = []; // Post types that can be synced (e.g., ['artwork', 'tattoo']): usually built by Registrar.php if the integration name exists as a key in [ 'integrations' => []]
+	protected array $syncTaxonomies = []; // Post types that can be synced (e.g., ['artwork', 'tattoo']): usually built by Registrar.php if the integration name exists as a key in [ 'integrations' => []]
 	protected array $contentTypes = [];   // Integration's available content types. Set by child classes' getContentTypes
 	protected bool $has_content = false; // Whether integration has content that can sync
 	/**
@@ -295,12 +298,10 @@
 		$postTypes = get_option($key, false);
 		if (!$postTypes) {
 			$postTypes = [];
-
-			// Get from JVB_CONTENT
-
-			foreach (JVB_CONTENT as $type => $config) {
-				if (array_key_exists('integrations', $config) && array_key_exists($this->service_name, $config['integrations'])) {
-					$postTypes[] = $type;
+			foreach (Registrar::getRegistered('post') as $registrar) {
+				$registrar = Registrar::getInstance($registrar);
+				if ($registrar->hasIntegration($this->service_name)) {
+					$postTypes[] = $registrar->getSlug();
 				}
 			}
 
@@ -318,11 +319,9 @@
 		if (!$taxonomies) {
 			// Combine both content and taxonomy filtering
 			$taxonomies = [];
-			// Get from JVB_TAXONOMY (content taxonomies)
-			foreach (JVB_TAXONOMY as $type => $config) {
-				if (jvbCheck('is_content', $config) &&
-					isset($config['integrations'][$this->service_name])) {
-					$taxonomies[] = $type;
+			foreach (Registrar::getFeatured('is_content', 'term') as $registrar) {
+				if ($registrar->hasIntegration($this->service_name)) {
+					$taxonomies[] = $registrar->getSlug();
 				}
 			}
 
@@ -2124,23 +2123,34 @@
 	 */
 	public function handleSavePost(int $postID, WP_Post $post, bool $update): void
 	{
-		if (jvbNoSaveIt($postID, $post)) {
+		if (!$postID || $postID === 0) {
 			return;
 		}
+		$postType = jvbNoBase($post->post_type);
+
+		if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
+		if (wp_is_post_revision($postID)) return;
+
+		$registrar = Registrar::getInstance($postType);
+		if (!$registrar){
+			return;
+		}
+
 		if (empty($this->syncPostTypes)) {
 			return;
 		}
 
-		$config = JVB_CONTENT[jvbNoBase($post->post_type)]??null;
-		if (!$config) {
-			return;
-		}
-
-		$settings = $config['integrations'][$this->service_name]??null;
+		$settings = $registrar->hasIntegration($this->service_name)??null;
 		if (!$settings) {
 			return;
 		}
 
+		$settings = $registrar->getIntegrationConfig($this->service_name);
+		if (!$settings){
+			return;
+		}
+
+
 		$fields = $this->getSyncFields($postID, 'post', ['schedule_'.$this->service_name]);
 		if (!$fields['share_to_'.$this->service_name]) {
 			return;
@@ -2243,13 +2253,13 @@
 		if (!in_array($noBase, $this->syncTaxonomies)) {
 			return;
 		}
-		// Check if taxonomy is content-type
-		$config = JVB_TAXONOMY[$noBase] ?? null;
-		if (!$config || !($config['is_content'] ?? false)) {
+		$registrar = Registrar::getInstance($noBase);
+		if (!$registrar->hasFeature('is_content')) {
 			return;
 		}
 
-		$settings = $config['integrations'][$this->service_name] ?? null;
+
+		$settings = $registrar->getIntegrationConfig($this->service_name);
 		if (!$settings) {
 			return;
 		}
@@ -3283,13 +3293,16 @@
 				echo Form::render($name, null, $config);
 			}
 			foreach ($this->syncPostTypes as $type) {
-				$config = JVB_CONTENT[$type];
+				$registrar = Registrar::getInstance($type);
+
+				$icon = $registrar->getIcon();
+				$icon = $icon === '' ? jvbDefaultIcon() : $icon;
 				?>
 				<details>
-					<summary><?= jvbIcon($config['icon']) ?><?= $config['singular']?> Defaults</summary>
+					<summary><?= jvbIcon($icon) ?><?= $registrar->getSingular()?> Defaults</summary>
 					<?php
-					$fields = new \JVBase\registry\providers\IntegrationFieldProvider();
-					$fields = $fields->getIntegrationFields($this->service_name, $config);
+					$fields = new AddIntegrationFields($this->service_name);
+					$fields = $fields->getIntegrationFields();
 					foreach($fields as $name=>$c) {
 						$c['required'] = false;
 						if ($c['type'] === 'number') {
@@ -3330,17 +3343,16 @@
 		$enabled = get_option($key);
 		if (!$enabled) {
 			$enabled = [];
-			foreach (array_merge(JVB_CONTENT, JVB_TAXONOMY) as $content => $config) {
-				if (!array_key_exists('integrations', $config)) {
+			foreach (Registrar::getRegistered() as $registrar) {
+				$registrar = Registrar::getInstance($registrar);
+				if (!$registrar->hasIntegration($this->service_name)) {
 					continue;
 				}
-				if (!array_key_exists($this->service_name, $config['integrations'])) {
+				$type = $registrar->getIntegration($this->service_name)->getContent_type();
+				if (!$type) {
 					continue;
 				}
-				if (!array_key_exists('content_type', $config['integrations'][$this->service_name])) {
-					continue;
-				}
-				$type = $config['integrations'][$this->service_name]['content_type'];
+
 				if (!in_array($type, $enabled)) {
 					$enabled[] = $type;
 				}
@@ -3516,4 +3528,18 @@
 	{
 		$this->token_refresh_attempted = false;
 	}
+
+	public function getAllowedContent():array
+	{
+		return $this->allowedContent;
+	}
+
+	/**
+	 * Used by JVBase\registrar\helpers\AddIntegrationFields.php
+	 * @return array
+	 */
+	public function getAdditionalFields(?string $content_type = null):array
+	{
+		return [];
+	}
 }
diff --git a/inc/integrations/Square.php b/inc/integrations/Square.php
index d889bfa..85032f3 100644
--- a/inc/integrations/Square.php
+++ b/inc/integrations/Square.php
@@ -4,6 +4,7 @@
 use JVBase\meta\Form;
 use JVBase\meta\Meta;
 use Exception;
+use JVBase\registrar\Registrar;
 use JVBase\registry\PostTypeRegistrar;
 use WP_Error;
 use JVBase\ui\Checkout;
@@ -1384,7 +1385,12 @@
 	 */
 	protected function getVariationMapping(string $post_type): array
 	{
-		$product_type = JVB_CONTENT[jvbNoBase($post_type)]['integrations']['square']['content_type'] ?? 'REGULAR';
+		$registrar = Registrar::getInstance($post_type));
+		if (!$registrar) {
+			return [];
+		}
+		$config = $registrar->getIntegrationConfig($this->service_name);
+		$product_type = $config['content_type']??'REGULAR';
 		$valid_fields = $this->getValidFieldsForProductType($product_type);
 
 		$defaults = [
@@ -1453,7 +1459,12 @@
 	 */
 	protected function getFieldMapping(string $post_type): array
 	{
-		$product_type = JVB_CONTENT[jvbNoBase($post_type)]['integrations']['square']['content_type'] ?? 'REGULAR';
+		$registrar = Registrar::getInstance($post_type));
+		if (!$registrar) {
+			return [];
+		}
+		$config = $registrar->getIntegrationConfig($this->service_name);
+		$product_type = $config['content_type']??'REGULAR';
 		$valid_fields = $this->getValidFieldsForProductType($product_type);
 
 		$defaults = [
@@ -1733,11 +1744,11 @@
 	public function trackUserLogin(string $user_login, \WP_User $user): void
 	{
 		// Check if user has Square integration
-		$roles = array_keys(JVB_USER);
-		$user_roles = $user->roles;
-
-		foreach ($user_roles as $role) {
-			if (isset(JVB_USER[$role]['integrations']['square']['is_customer'])) {
+		$role = jvbUserRole($user->ID);
+		$registrar = Registrar::getInstance($role);
+		if ($registrar) {
+			$config = $registrar->getIntegration($this->service_name);
+			if ($config->isCustomer()) {
 				$login_count = (int)get_user_meta($user->ID, BASE . '_square_login_count', true);
 				$login_count++;
 
@@ -1748,8 +1759,6 @@
 				if ($login_count % self::PASSWORD_RESET_INTERVAL === 0) {
 					$this->schedulePasswordReset($user->ID);
 				}
-
-				break;
 			}
 		}
 	}
@@ -2236,16 +2245,21 @@
 	 */
 	private function importSquareItem(array $item): bool|int
 	{
+		//TODO: We need to add the post type to custom meta for Square, this is not good if we have multiple post types with the same product type
 		// Find matching content type
 		$product_type = $item['item_data']['product_type'] ?? 'REGULAR';
 		$post_type = null;
 
-		foreach (JVB_CONTENT as $key => $config) {
-			if (isset($config['integrations']['square']['content_type']) &&
-				$config['integrations']['square']['content_type'] === $product_type) {
-				$post_type = jvbCheckBase($key);
+		foreach (Registrar::getRegistered() as $registrar) {
+			if (!$registrar->hasIntegration($this->service_name)) {
+				continue;
+			}
+			$config = $registrar->getIntegration($this->service_name);
+			if ($config->getContent_type() && $config->getContent_type() === $product_type) {
+				$post_type = jvbCheckBase($registrar->getSlug());
 				break;
 			}
+
 		}
 
 		if (!$post_type) {
@@ -2746,6 +2760,23 @@
 			'GIFT_CARD'	=> array_merge($this->setGiftCardFields())
 		];
 	}
+	public function getAdditionalFields(?string $content_type = null):array {
+		if ($content_type && array_key_exists($content_type, $this->contentTypes)){
+			$array = $this->contentTypes[$content_type];
+			return array_combine(
+				array_map(fn($k) => 'sq_' . $k, array_keys($array)),
+				$array
+			);
+		} else if ($content_type && !array_key_exists($content_type, $this->contentTypes)) {
+			error_log('Could not get default fields for '.$this->service_name.' content type: '.$content_type);
+			return [];
+		}
+		$array = $this->setBaseFields();
+		return array_combine(
+			array_map(fn($k) => 'sq_' . $k, array_keys($array)),
+			$array
+		);
+	}
 	protected function setBaseFields():array
 	{
 		return [
diff --git a/inc/managers/AdminPages.php b/inc/managers/AdminPages.php
index 866893c..a584458 100644
--- a/inc/managers/AdminPages.php
+++ b/inc/managers/AdminPages.php
@@ -1,6 +1,7 @@
 <?php
 namespace JVBase\managers;
 
+use JVBase\registrar\Registrar;
 use JVBase\utility\Features;
 use JVBase\rest\Route;
 use JVBase\rest\PermissionHandler;
@@ -219,6 +220,11 @@
         }
     }
 
+	public function getMainConfig():array
+	{
+		return $this->main_page;
+	}
+
     /**
      * Render the main settings page
      */
@@ -314,11 +320,11 @@
         </li>
         <li>
             <span class="status-label">Content Types:</span>
-            <span class="status-value"><?= count(JVB_CONTENT); ?> registered</span>
+            <span class="status-value"><?= count(Registrar::getRegistered('post')); ?> registered</span>
         </li>
         <li>
             <span class="status-label">Taxonomies:</span>
-            <span class="status-value"><?= count(JVB_TAXONOMY); ?> registered</span>
+            <span class="status-value"><?= count(Registrar::getRegistered('term')); ?> registered</span>
         </li>
         <?php
     }
@@ -350,10 +356,8 @@
         global $wpdb;
 
         $week_ago = date('Y-m-d H:i:s', strtotime('-7 days'));
-		$content_types = [];
-		foreach (JVB_CONTENT as $content => $config) {
-			$content_types[jvbCheckBase($content)] = $config['plural'];
-		}
+		$content_types = array_map(function ($type) { return jvbCheckBase($type); },
+			Registrar::getRegistered('post'));
 
         ?>
         <table class="jvb-content-table">
@@ -605,22 +609,12 @@
 	{
 		$group = jvbNoBase($group);
 
-		if (defined('JVB_CONTENT')) {
-			foreach (JVB_CONTENT as $key => $config) {
-				if (jvbNoBase($key) === $group) {
-					return true;
-				}
+		$registered = Registrar::getRegistered();
+		foreach ($registered as $r) {
+			if ($r === $group) {
+				return true;
 			}
 		}
-
-		if (defined('JVB_TAXONOMY')) {
-			foreach (JVB_TAXONOMY as $key => $config) {
-				if (jvbNoBase($key) === $group) {
-					return true;
-				}
-			}
-		}
-
 		return false;
 	}
 
diff --git a/inc/managers/CRUDManager.php b/inc/managers/CRUDManager.php
index cec8688..3cbf9a7 100644
--- a/inc/managers/CRUDManager.php
+++ b/inc/managers/CRUDManager.php
@@ -1,8 +1,8 @@
 <?php
 namespace JVBase\managers;
 
+use JVBase\registrar\Registrar;
 use JVBase\ui\CRUDSkeleton;
-use JVBase\utility\Features;
 
 if (!defined('ABSPATH')) {
 	exit;
@@ -15,29 +15,19 @@
 class CRUD {
 	protected CRUDSkeleton $skeleton;
 	protected Cache $cache;
-	protected array $config;
 	protected string $content;
 	protected array $taxonomies = [];
 	protected int $user_id;
-	protected ?string $type = null;
-	protected ?array $constant = null;
+	protected Registrar $registrar;
 
 	public function __construct(string $content) {
-		if (array_key_exists($content, JVB_CONTENT)) {
-			$this->type = 'post';
-			$this->constant = JVB_CONTENT;
-		} elseif (array_key_exists($content, JVB_TAXONOMY)) {
-			$this->type = 'term';
-			$this->constant = JVB_TAXONOMY;
-		} elseif (array_key_exists($content, JVB_USER)) {
-			$this->type = 'user';
-			$this->constant = JVB_USER;
-		} else {
+		$this->registrar = Registrar::getInstance($content);
+
+		if (!$this->registrar) {
 			return;
 		}
 
 		$this->user_id = get_current_user_id();
-		$this->config = $this->constant[$content];
 		$this->content = $content;
 		$this->cache = Cache::for('crud')->connect('post')->connect('taxonomy');
 
@@ -56,33 +46,32 @@
 	protected function configure(): void {
 		// Basic info
 		$this->skeleton
-			->content($this->content, $this->config['singular'], $this->config['plural'])
+			->content($this->content, $this->registrar->getSingular(), $this->registrar->getPlural())
 			->title(
-				'Your ' . $this->config['plural'],
-				$this->config['page_description'] ?? ''
+				$this->registrar->config('dashboard')->getTitle(),
+				$this->registrar->config('dashboard')->getDescription()?? ''
 			);
 
 		// Initialize meta
 		$this->skeleton->addSearch();
 
 		// Timeline if applicable
-		if (Features::forContent($this->content)->has('is_timeline')) {
+		if ($this->registrar && $this->registrar->hasFeature('is_timeline')) {
 			$this->skeleton->setTimeline();
 		}
 
 		// Fields and sections
-		$this->skeleton->setFields($this->config['fields']);
+		$this->skeleton->setFields($this->registrar->getFields());
 
-		$sections = array_key_exists('sections', $this->config) ? $this->config['sections'] : [];
-		foreach ($sections as $id => $config) {
-			$this->skeleton->addSection($id, $config);
+		foreach ($this->registrar->getSections() as $config) {
+			$this->skeleton->addSection($config['id'], $config);
 		}
 
 		// Taxonomies
 		$this->initTaxonomies();
 
 		// Statuses
-		if (Features::forContent($this->content)->has('is_calendar')) {
+		if ($this->registrar && $this->registrar->hasFeature('is_calendar')) {
 			$this->skeleton->setCalendar();
 		}
 
@@ -104,7 +93,7 @@
 		// Capabilities
 		$this->skeleton->addCapabilities(['view', 'edit', 'create', 'delete']);
 
-		$plural = strtolower($this->config['plural'] ?? $this->content . 's');
+		$plural = strtolower($this->registrar->getPlural() ?? $this->content . 's');
 		$canPublish = jvbUserIsVerified() && user_can($this->user_id, "publish_{$plural}");
 		$this->skeleton->userCanPublish($canPublish);
 
@@ -112,7 +101,10 @@
 		$this->skeleton->addBulkActions(['edit', 'publish', 'draft', 'trash']);
 
 		// Uploader
-		$this->setupUploader();
+		if ($this->registrar->getType() === 'post') {
+			$this->setupUploader();
+		}
+
 
 		// Sticky fields
 		$stuck = ['post_title', 'term_name'];
@@ -129,29 +121,30 @@
 	 * Setup uploader configuration
 	 */
 	protected function setupUploader(): void {
-		$isSingleImage = jvbCheck('single_image', $this->config);
+
+		$isSingleImage = $this->registrar->hasFeature('single_image');
 
 		$config = [
 			'type' => 'upload',
 			'subtype' => 'image',
 			'mode' => $isSingleImage ? 'direct' : 'selection',
 			'create_new' => true,
-			'label' => $this->config['upload_title'] ?? 'Bulk Upload ' . $this->config['plural'],
+			'label' => $this->registrar->getUploadTitle(),
 			'content' => $this->content,
-			'singular' => $this->config['singular'],
-			'plural' => $this->config['plural'],
+			'singular' => $this->registrar->getSingular(),
+			'plural' => $this->registrar->getPlural(),
 			'multiple' => true,
 			'destination' => $isSingleImage ? 'post' : 'post_group'
 		];
 
 		if (!$isSingleImage) {
-			$config['upload_text'] = '<p>Drag images into groups. Each group becomes its own ' . $this->config['singular'] . '.</p>
+			$config['upload_text'] = '<p>Drag images into groups. Each group becomes its own ' . $this->registrar->getSingular() . '.</p>
 				<p>You can also select multiple images and click the "Add to Group" button.</p>
-				<p>If a ' . $this->config['singular'] . ' has multiple images, you can select the ' . jvbDashIcon('star') . ' to set an image as the main one.</p>
-				<p>Images left ungrouped will become individual ' . $this->config['plural'] . '</p>
+				<p>If a ' . $this->registrar->getSingular() . ' has multiple images, you can select the ' . jvbDashIcon('star') . ' to set an image as the main one.</p>
+				<p>Images left ungrouped will become individual ' . $this->registrar->getPlural() . '</p>
 				<p>Once finished, click the \'Save Changes\' button to send to server for processing.</p>';
 		} else {
-			$config['description'] = 'Each image will become its own ' . $this->config['singular'] . '.';
+			$config['description'] = 'Each image will become its own ' . $this->registrar->getSingular() . '.';
 		}
 
 		$this->skeleton->addUploader($config);
@@ -161,60 +154,7 @@
 	 * Initialize taxonomies from WordPress config
 	 */
 	protected function initTaxonomies(): void {
-		$this->taxonomies = array_filter(JVB_TAXONOMY, function ($config) {
-			return in_array($this->content, $config['for_content']);
-		});
-	}
-
-	/**
-	 * Get statuses - calendar or standard
-	 */
-	protected function getStatuses(): array {
-		return array_key_exists('is_calendar', $this->config) ?
-			[
-				'all' => [
-					'icon' => 'calendar',
-					'label' => 'Everything',
-				],
-				'future' => [
-					'label' => 'Upcoming',
-					'icon' => 'clock-clockwise',
-				],
-				'past' => [
-					'label' => 'Past',
-					'icon' => 'clock-counter-clockwise',
-				],
-				'repeat' => [
-					'label' => 'Recurring',
-					'icon' => 'repeat',
-				],
-				'draft' => [
-					'icon' => 'eye-closed',
-					'label' => 'Hidden',
-				],
-				'trash' => [
-					'label' => 'Scrapped',
-					'icon' => 'trash',
-				],
-			] :
-			[
-				'all' => [
-					'icon' => 'infinity',
-					'label' => 'Everything',
-				],
-				'publish' => [
-					'icon' => 'eye',
-					'label' => 'Live',
-				],
-				'draft' => [
-					'icon' => 'eye-closed',
-					'label' => 'Hidden',
-				],
-				'trash' => [
-					'label' => 'Scrapped',
-					'icon' => 'trash',
-				],
-			];
+		$this->taxonomies = $this->registrar->registrar->taxonomies;
 	}
 
 	/**
@@ -222,9 +162,9 @@
 	 */
 	public function createItem(array $actions): array {
 		$actions[] = [
-			'button' => '<button type="button" class="create-item row" title="Create New ' . $this->config['singular'] . '">'
+			'button' => '<button type="button" class="create-item row" title="Create New ' . $this->registrar->getSingular() . '">'
 				. jvbDashIcon('plus-square')
-				. '<span class="screen-reader-text">Create New ' . $this->config['singular'] . '</span></button>',
+				. '<span class="screen-reader-text">Create New ' . $this->registrar->getSingular() . '</span></button>',
 			'content' => '', // Modal is rendered by skeleton
 		];
 
diff --git a/inc/managers/CacheManagerOld.php b/inc/managers/CacheManagerOld.php
deleted file mode 100644
index d1cf099..0000000
--- a/inc/managers/CacheManagerOld.php
+++ /dev/null
@@ -1,856 +0,0 @@
-<?php
-namespace JVBase\managers;
-
-if (!defined('ABSPATH')) {
-	exit;
-}
-
-/**
- * Manages HTTP cache timestamps and relationship-based invalidation
- *
- * Data caching: Use wrapper methods or wp_cache_get/set directly
- * HTTP caching: This class manages timestamps for ETag/Last-Modified headers
- */
-class CacheManager
-{
-	private const CONNECTIONS_OPTION = BASE.'cache_connections';
-	private static ?array $connections_cache = null; // Cache in memory
-	private string $prefix = BASE;
-	private string $group;
-	private int $cache_ttl;
-	private static ?bool $use_object_cache = null;
-	private static array $instances = []; // Cache instances per type
-	private static array $http_timestamps = []; // Request-level memory cache
-	private static ?CacheManager $singleton = null;
-
-	/**
-	 * Private constructor - use for() factory method instead
-	 */
-	private function __construct(string $group, ?int $ttl = null)
-	{
-		$this->group = jvbNoBase($group);
-		$this->cache_ttl = $ttl ?: 3600;
-
-		if (is_null(static::$use_object_cache)) {
-			static::$use_object_cache = wp_using_ext_object_cache();
-		}
-
-		add_action('init', [$this, 'registerHooks']);
-	}
-
-	/**
-	 * Get singleton instance (for general cache operations)
-	 * For type-specific operations, use for() or forUser() instead
-	 */
-	public static function getInstance(): self
-	{
-		if (self::$singleton === null) {
-			self::$singleton = new self('global', HOUR_IN_SECONDS);
-		}
-		return self::$singleton;
-	}
-
-	/**
-	 * Get all cache connections (public accessor)
-	 *
-	 * @return array Array of cache group connections
-	 */
-	public static function getAllConnections(): array
-	{
-		return self::getConnections();
-	}
-
-	/**
-	 * Get all registered cache groups
-	 *
-	 * @return array List of cache group names
-	 */
-	public static function getAllGroups(): array
-	{
-		$connections = self::getConnections();
-		return array_keys($connections);
-	}
-	/**
-	 * Register WordPress hooks for automatic cache invalidation
-	 * Call this once during plugin initialization
-	 */
-	public static function registerHooks(): void
-	{
-		// Post updates (all post types including core)
-		add_action('save_post', [self::class, 'onPostSave'], 10, 2);
-		add_action('delete_post', [self::class, 'onPostDelete']);
-		// Meta updates (will catch MetaManager updates)
-		add_action('updated_post_meta', [self::class, 'onPostMetaUpdate'], 10, 4);
-		add_action('added_post_meta', [self::class, 'onPostMetaUpdate'], 10, 4);
-		add_action('deleted_post_meta', [self::class, 'onPostMetaDelete'], 10, 4);
-		// transition_post_status?
-
-		// Term updates (all taxonomies)
-		add_action('edited_term', [self::class, 'onTermSave'], 10, 3);
-		add_action('create_term', [self::class, 'onTermSave'], 10, 3);
-		add_action('delete_term', [self::class, 'onTermDelete'], 10, 3);
-
-		// Term meta updates
-		add_action('updated_term_meta', [self::class, 'onTermMetaUpdate'], 10, 4);
-		add_action('added_term_meta', [self::class, 'onTermMetaUpdate'], 10, 4);
-		add_action('deleted_term_meta', [self::class, 'onTermMetaDelete'], 10, 4);
-
-		// User updates
-		add_action('profile_update', [self::class, 'onUserUpdate'], 10, 2);
-		add_action('user_register', [self::class, 'onUserUpdate'], 10, 1);
-		add_action('deleted_user', [self::class, 'onUserDelete']);
-
-		// User meta updates
-		add_action('updated_user_meta', [self::class, 'onUserMetaUpdate'], 10, 4);
-		add_action('added_user_meta', [self::class, 'onUserMetaUpdate'], 10, 4);
-		add_action('deleted_user_meta', [self::class, 'onUserMetaDelete'], 10, 4);
-	}
-
-	/**
-	 * Get or create a cache manager instance for a content type
-	 *
-	 * @param string $type Content type (tattoo, style, etc.)
-	 * @param int|null $ttl Optional TTL override
-	 * @return self Fluent interface
-	 */
-	public static function for(string $type, ?int $ttl = null): self
-	{
-		$type = jvbNoBase($type);
-		$key = $type . ($ttl ? "_{$ttl}" : '');
-
-		if (!isset(self::$instances[$key])) {
-			self::$instances[$key] = new self($type, $ttl);
-		}
-
-		return self::$instances[$key];
-	}
-
-	/**
-	 * Get cache manager for a specific user
-	 * Each user gets their own cache group for complete isolation
-	 *
-	 * @param int $user_id User ID
-	 * @param int|null $ttl Optional TTL
-	 * @return self
-	 */
-	public static function forUser(int $user_id, ?int $ttl = null): self
-	{
-		return self::for("user_{$user_id}", $ttl);
-	}
-
-	/**
-	 * Get HTTP cache timestamp for content type(s)
-	 * Used for ETag and Last-Modified header generation
-	 *
-	 * @param string|array $types Single type or array of types
-	 * @return int Latest timestamp (Unix time)
-	 */
-	public static function getTimestamp(string|array $types): int
-	{
-		// Multiple types - return latest
-		if (is_array($types)) {
-			$latest = 0;
-			foreach ($types as $type) {
-				$timestamp = self::getTimestamp($type);
-				if ($timestamp > $latest) {
-					$latest = $timestamp;
-				}
-			}
-			return $latest ?: time();
-		}
-
-		$type = jvbNoBase($types);
-
-		// Check request-level cache
-		if (isset(self::$http_timestamps[$type])) {
-			return self::$http_timestamps[$type];
-		}
-
-		// Load from cache (Redis or transient - wp_cache handles it)
-		$timestamp = (int)wp_cache_get("http_ts_{$type}", 'jvb_timestamps') ?: time();
-
-		// Cache in memory for this request
-		self::$http_timestamps[$type] = $timestamp;
-
-		return $timestamp;
-	}
-
-	/**
-	 * Update HTTP cache timestamp (marks content as modified)
-	 *
-	 * @param string $type Content type
-	 * @return int The new timestamp
-	 */
-	public static function updateTimestamp(string $type): int
-	{
-		$type = jvbNoBase($type);
-		$timestamp = time();
-
-		// Store (Redis or transient - wp_cache handles it)
-		wp_cache_set("http_ts_{$type}", $timestamp, 'jvb_timestamps', WEEK_IN_SECONDS);
-
-		// Update request cache
-		self::$http_timestamps[$type] = $timestamp;
-
-		do_action('jvb_http_timestamp_updated', $type, $timestamp);
-
-		return $timestamp;
-	}
-
-	/**
-	 * Invalidate cache for a content type
-	 *
-	 * @param string $type Content type to invalidate
-	 * @param string|array|null $specific_keys Optional specific key(s) to delete without flushing group
-	 * @param bool $flush_connections Whether to flush connected caches
-	 * @return void
-	 */
-	public static function invalidateAll(string $type, $specific_keys = null, bool $flush_connections = true): void
-	{
-		$type = jvbNoBase($type);
-
-		// Update HTTP timestamp
-		self::updateTimestamp($type);
-
-		// If specific keys provided, only delete those
-		if ($specific_keys !== null) {
-			$instance = self::for($type);
-			if (is_array($specific_keys)) {
-				foreach ($specific_keys as $key) {
-					$instance->delete($key);
-				}
-			} else {
-				$instance->delete($specific_keys);
-			}
-		} else {
-			// Flush the entire group
-			if (function_exists('wp_cache_flush_group')) {
-				wp_cache_flush_group($type);
-			} else {
-				wp_cache_flush();
-			}
-		}
-
-		// Flush connected caches
-		if ($flush_connections) {
-			self::for($type)->connections();
-		}
-
-		do_action('jvb_cache_invalidated', $type);
-	}
-
-	/**
-	 * Invalidate only specific keys for a type (doesn't flush group or update timestamp)
-	 * Use this when you want surgical cache invalidation
-	 *
-	 * @param string $type Content type
-	 * @param string|array $keys Key(s) to delete
-	 * @return void
-	 */
-	public static function invalidateKeys(string $type, string|array $keys): void
-	{
-		$instance = self::for($type);
-
-		if (is_array($keys)) {
-			foreach ($keys as $key) {
-				$instance->delete($key);
-			}
-		} else {
-			$instance->delete($keys);
-		}
-	}
-
-	/**
-	 * Fluent instance method to invalidate this cache type
-	 *
-	 * @param string|array|null $specific_keys Optional specific key(s)
-	 * @param bool $flush_connections Whether to flush connected caches
-	 * @return self For chaining
-	 */
-	public function invalidate($specific_keys = null, bool $flush_connections = true): self
-	{
-		self::invalidateAll($this->group, $specific_keys, $flush_connections);
-		return $this;
-	}
-
-	/**
-	 * Get the HTTP timestamp for this instance's type
-	 *
-	 * @return int
-	 */
-	public function timestamp(): int
-	{
-		return self::getTimestamp($this->group);
-	}
-
-	/**
-	 * Update the HTTP timestamp for this instance's type
-	 *
-	 * @return self For chaining
-	 */
-	public function touch(): self
-	{
-		self::updateTimestamp($this->group);
-		return $this;
-	}
-
-	/**
-	 * Get a value from the cache
-	 * @param string|array $key The key to look up (auto-generates key from array of key=>values)
-	 * @param string|null $group The group to get from. Defaults to current group
-	 * @return mixed
-	 */
-	public function get(string|array $key, ?string $group = null): mixed
-	{
-		$group = $group ?: $this->group;
-		$key = $this->normalizeKey($key);
-		$cache_key = $this->buildKey($key);
-
-		$value = wp_cache_get($cache_key, $group);
-
-		// Fallback to transient if no external object cache
-		if ($value === false && !wp_using_ext_object_cache()) {
-			$value = get_transient($group . '_' . $cache_key);
-		}
-
-		return $value;
-	}
-
-	/**
-	 * Store a value in cache
-	 * @param string|array $key The key to look up (auto-generates key from array of key=>values)
-	 * @param mixed $value The Value to set
-	 * @param int|null $ttl The ttl (defaults to current set ttl)
-	 * @param string|null $group The group to add cache to (defaults to current group))
-	 * @return bool
-	 */
-	public function set(string|array $key, mixed $value, ?int $ttl = null, ?string $group = null): bool
-	{
-		$ttl = $ttl ?: $this->cache_ttl;
-		$group = $group ?: $this->group;
-		$key = $this->normalizeKey($key);
-		$cache_key = $this->buildKey($key);
-
-		self::updateTimestamp($this->group);
-
-		// Try object cache first
-		$result = wp_cache_set($cache_key, $value, $group, $ttl);
-
-		// If no external object cache, also store in transient for persistence
-		if (!wp_using_ext_object_cache()) {
-			set_transient($group . '_' . $cache_key, $value, $ttl);
-		}
-
-		return $result;
-	}
-	/**
-	 * Delete a cached value
-	 * @param string|array $key The key to look up (auto-generates key from array of key=>values)
-	 * @param string|null $group The group to delete from (defaults to current group)
-	 * @return bool
-	 */
-	public function delete(string|array $key, ?string $group = null): bool
-	{
-		$group = $group ?: $this->group;
-		$key = $this->normalizeKey($key);
-		$cache_key = $this->buildKey($key);
-
-		$result = wp_cache_delete($cache_key, $group);
-
-		// Also delete transient if no external object cache
-		if (!wp_using_ext_object_cache()) {
-			delete_transient($group . '_' . $cache_key);
-		}
-
-		return $result;
-	}
-
-
-	/**
-	 * Clear all cache for this group
-	 * @return bool
-	 */
-	public function clear(): bool
-	{
-		try {
-			if (function_exists('wp_cache_flush_group')) {
-				wp_cache_flush_group($this->group);
-			}
-
-			// Clear transients for this group if no external object cache
-			if (!wp_using_ext_object_cache()) {
-				$this->clearGroupTransients();
-			}
-
-			self::updateTimestamp($this->group);
-			return true;
-		} catch (\Exception $e) {
-			return false;
-		}
-	}
-
-	/**
-	 * Clear all transients for this cache group
-	 */
-	private function clearGroupTransients(): void
-	{
-		global $wpdb;
-
-		$pattern = '_transient_' . $this->group . '_' . $this->prefix . '%';
-		$timeout_pattern = '_transient_timeout_' . $this->group . '_' . $this->prefix . '%';
-
-		$wpdb->query(
-			$wpdb->prepare(
-				"DELETE FROM $wpdb->options WHERE option_name LIKE %s OR option_name LIKE %s",
-				$pattern,
-				$timeout_pattern
-			)
-		);
-	}
-
-	/**
-	 * Helper to generateKey from array if applicable
-	 * @param string|array $key
-	 * @return string
-	 */
-	private function normalizeKey(string|array $key): string
-	{
-		return is_array($key) ? $this->generateKey($key) : $key;
-	}
-
-	/**
-	 * Generate a cache key from parameters
-	 * @param array $params An array of key/values that differentiates this cache item from others
-	 * @return string
-	 */
-	public function generateKey(array $params): string
-	{
-		// Sort params for consistent key generation
-		ksort($params);
-		return md5(serialize($params));
-	}
-
-	/**
-	 * The workhorse shorthand of CacheManager. Tests the cache, and calls the callback if nothing is found.
-	 * @param string|array $key The key to look up (auto-generates key from array of key=>values)
-	 * @param callable $callback The callback to generate the value for this key
-	 * @param int|null $ttl The time-to-live for the cache. Defaults to constructor
-	 * @param string|null $group The group to save cache to. Defaults to constructor
-	 * @return mixed
-	 */
-	public function remember(string|array $key, callable $callback, ?int $ttl = null, ?string $group = null): mixed
-	{
-		$group = $group ?: $this->group;
-		$ttl = $ttl ?: $this->cache_ttl;
-		$key = $this->normalizeKey($key);
-
-		$value = $this->get($key, $group);
-
-		if ($value === false) {
-			$value = $callback();
-			if ($value !== false && $value !== null) {
-				$this->set($key, $value, $ttl, $group);
-			}
-		}
-
-		return $value;
-	}
-
-	/**
-	 * Build the cache key
-	 * @param string $key
-	 * @return string
-	 */
-	private function buildKey(string $key): string
-	{
-		return $this->prefix . $key;
-	}
-
-	/**
-	 * Get instance group name (for debugging)
-	 */
-	public function getGroup(): string
-	{
-		return $this->group;
-	}
-
-
-	/***************************************************************************
-	 * CONNECTIONS
-	 * Connect to other caches by instantiating and defining connection
-	 * Ex: CacheManager::for('usernames')->connectTo($type, $scope = 'all', $keyPattern)
-	 * Where: 	$type = content / taxonomy / user
-	 * 			$scope = either 'id' for specific item, or the entire group (registered post type, taxonomy, or user role)
-	 * 			$keyPattern = ??
-	 ***************************************************************************/
-	/**
-	 * Define a connection between cache groups
-	 * Connected caches will have their ID-based keys deleted when this cache invalidates
-	 *
-	 * @param string $type Grand overview ('post', 'taxonomy', 'user')
-	 * @param string $scope Type-specific constant, user role, or 'id'
-	 * @return self For chaining
-	 */
-	public function connectTo(string $type, string $scope = 'id'): self
-	{
-		//TODO: Handle connect to where $type === 'all'
-		$connections = self::getConnections();
-
-		if (!isset($connections[$this->group])) {
-			$connections[$this->group] = [];
-		}
-
-		$new_connection = [
-			'parent' => $type,
-			'scope' => $scope
-		];
-
-		// Check if already exists
-		foreach ($connections[$this->group] as $existing) {
-			if ($existing === $new_connection) {
-				return $this;
-			}
-		}
-
-		$connections[$this->group][] = $new_connection;
-		update_option(self::CONNECTIONS_OPTION, $connections, false);
-		self::$connections_cache = $connections;
-
-		return $this;
-	}
-
-	/**
-	 * Get all registered connections (cached for performance)
-	 *
-	 * @param bool $refresh Force refresh from database
-	 * @return array
-	 */
-	private static function getConnections(bool $refresh = false): array
-	{
-		if (self::$connections_cache === null || $refresh) {
-			self::$connections_cache = get_option(self::CONNECTIONS_OPTION, []);
-		}
-
-		return self::$connections_cache;
-	}
-
-	/**
-	 * Flush all caches connected to this one
-	 *
-	 * @return self For chaining
-	 */
-	public function connections(): self
-	{
-		$all_connections = self::getConnections();
-
-		foreach ($all_connections as $cache_group => $connections) {
-			foreach ($connections as $conn) {
-				if ($this->matchesConnection($conn)) {
-					$this->flushConnection($cache_group, $conn);
-				}
-			}
-		}
-
-		return $this;
-	}
-
-	/**
-	 * Check if this cache group matches a connection definition
-	 */
-	private function matchesConnection(array $connection): bool
-	{
-		$parent = $connection['parent'] ?? '';
-		$scope = $connection['scope'] ?? 'id';
-
-		// Grand overview match
-		if ($this->group === $parent) {
-			return true;
-		}
-
-		// Type-specific match
-		if ($scope !== 'id') {
-			if ($this->group === jvbNoBase($scope)) {
-				return true;
-			}
-
-			// Check constants
-			if ($parent === 'post' && defined('JVB_CONTENT')) {
-				return isset(JVB_CONTENT[$scope]) && jvbNoBase($scope) === $this->group;
-			}
-
-			if ($parent === 'taxonomy' && defined('JVB_TAXONOMY')) {
-				return isset(JVB_TAXONOMY[$scope]) && jvbNoBase($scope) === $this->group;
-			}
-		}
-
-		// ID-specific match: 'user_123' matches 'user' + 'id'
-		if ($scope === 'id' && str_starts_with($this->group, $parent . '_')) {
-			return true;
-		}
-
-		return false;
-	}
-
-	/**
-	 * Flush a connected cache group
-	 * For ID-specific connections, deletes the specific ID key
-	 * For type/overview connections, flushes entire group
-	 */
-	private function flushConnection(string $cache_group, array $connection): void
-	{
-		$scope = $connection['scope'] ?? 'id';
-
-		// ID-specific: delete specific key
-		if ($scope === 'id') {
-			$id = $this->extractIdFromGroup();
-
-			if ($id !== null) {
-				self::invalidateKeys($cache_group, $id);
-				return;
-			}
-		}
-
-		// Type/overview: flush entire group
-		self::invalidateAll($cache_group, specific_keys: null, flush_connections: false);
-	}
-
-	/**
-	 * Extract ID from group name like 'user_123' -> '123'
-	 *
-	 * @return string|null
-	 */
-	private function extractIdFromGroup(): ?string
-	{
-		if (preg_match('/^[a-z]+_(\d+)$/', $this->group, $matches)) {
-			return $matches[1];
-		}
-
-		return null;
-	}
-
-	/**
-	 * Register multiple connections at once
-	 */
-	public static function registerConnections(array $connections): void
-	{
-		$existing = self::getConnections();
-		$changed = false;
-
-		foreach ($connections as $cache_group => $configs) {
-			if (!isset($existing[$cache_group])) {
-				$existing[$cache_group] = [];
-			}
-
-			foreach ($configs as $config) {
-				$duplicate = false;
-				foreach ($existing[$cache_group] as $existing_config) {
-					if ($existing_config === $config) {
-						$duplicate = true;
-						break;
-					}
-				}
-
-				if (!$duplicate) {
-					$existing[$cache_group][] = $config;
-					$changed = true;
-				}
-			}
-		}
-
-		if ($changed) {
-			update_option(self::CONNECTIONS_OPTION, $existing, false);
-			self::$connections_cache = $existing;
-		}
-	}
-
-	/**
-	 * Handle post save/update
-	 */
-	public static function onPostSave(int $post_id, \WP_Post $post): void
-	{
-		// Skip revisions and autosaves
-		if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) {
-			return;
-		}
-
-		$post_type = jvbNoBase($post->post_type);
-
-		// Invalidate post type cache
-		self::invalidateAll($post_type);
-
-		// Invalidate specific post cache
-		self::invalidateAll($post_id);
-		// Clear WordPress core post object cache
-		clean_post_cache($post_id);
-	}
-
-	/**
-	 * Handle post deletion
-	 */
-	public static function onPostDelete(int $post_id): void
-	{
-		$post = get_post($post_id);
-		if (!$post) {
-			return;
-		}
-
-		$post_type = jvbNoBase($post->post_type);
-
-		self::invalidateAll($post_type);
-		self::invalidateAll($post_id);
-		// Clear WordPress core post object cache
-		clean_post_cache($post_id);
-	}
-
-	/**
-	 * Handle term save/update
-	 */
-	public static function onTermSave(int $term_id, int $tt_id, string $taxonomy): void
-	{
-		// Clear WordPress core term cache
-		clean_term_cache($term_id, $taxonomy);
-		$taxonomy = jvbNoBase($taxonomy);
-
-		// Invalidate taxonomy cache
-		self::invalidateAll($taxonomy);
-
-		// Invalidate specific term cache
-		self::invalidateAll($term_id);
-	}
-
-	/**
-	 * Handle term deletion
-	 */
-	public static function onTermDelete(int $term_id, int $tt_id, string $taxonomy): void
-	{
-		// Clear WordPress core term cache
-		clean_term_cache($term_id, $taxonomy);
-		$taxonomy = jvbNoBase($taxonomy);
-
-		self::invalidateAll($taxonomy);
-		self::invalidateAll($term_id);
-	}
-
-	/**
-	 * Handle user update
-	 */
-	public static function onUserUpdate(int $user_id, ?\WP_User $old_user_data = null): void
-	{
-		// Invalidate user-specific cache
-		self::invalidateAll($user_id);
-
-		// Invalidate user role caches if roles changed
-		if ($old_user_data) {
-			$user = get_userdata($user_id);
-			if ($user && $user->roles !== $old_user_data->roles) {
-				foreach (array_merge($user->roles, $old_user_data->roles) as $role) {
-					self::invalidateAll($role);
-				}
-			}
-		}
-		// Clear WordPress core user cache
-		clean_user_cache($user_id);
-	}
-
-	/**
-	 * Handle user deletion
-	 */
-	public static function onUserDelete(int $user_id): void
-	{
-		self::invalidateAll($user_id);
-		// Clear WordPress core user cache
-		clean_user_cache($user_id);
-	}
-
-	/**
-	 * Handle post meta updates
-	 */
-	public static function onPostMetaUpdate(int $meta_id, int $post_id, string $meta_key, mixed $meta_value): void
-	{
-		if (!str_starts_with($meta_key, BASE)) {
-			return;
-		}
-
-		$post = get_post($post_id);
-		if (!$post) {
-			return;
-		}
-
-		self::onPostSave($post_id, $post);
-	}
-	public static function onPostMetaDelete(array $meta_ids, int $post_id, string $meta_key, mixed $meta_value):void
-	{
-		if (!str_starts_with($meta_key, BASE)) {
-			return;
-		}
-
-		$post = get_post($post_id);
-		if (!$post) {
-			return;
-		}
-
-		self::onPostSave($post_id, $post);
-	}
-
-	/**
-	 * Handle term meta updates
-	 */
-	public static function onTermMetaUpdate(int $meta_id, int $term_id, string $meta_key, mixed $meta_value): void
-	{
-		if (!str_starts_with($meta_key, BASE)) {
-			return;
-		}
-
-		$term = get_term($term_id);
-		if (!$term || is_wp_error($term)) {
-			return;
-		}
-
-		self::onTermSave($term_id, $term->term_taxonomy_id, $term->taxonomy);
-	}
-
-	public static function onTermMetaDelete(array $meta_ids, int $term_id, string $meta_key, mixed $meta_value):void
-	{
-		if (!str_starts_with($meta_key, BASE)) {
-			return;
-		}
-
-		$term = get_term($term_id);
-		if (!$term || is_wp_error($term)) {
-			return;
-		}
-
-		self::onTermSave($term_id, $term->term_taxonomy_id, $term->taxonomy);
-	}
-
-	/**
-	 * Handle user meta updates
-	 */
-	public static function onUserMetaUpdate(int $meta_id, int $user_id, string $meta_key, mixed $meta_value): void
-	{
-		if (!str_starts_with($meta_key, BASE)) {
-			return;
-		}
-
-		$user = get_userdata($user_id);
-		if (!$user) {
-			return;
-		}
-
-		self::onUserUpdate($user_id, null);
-	}
-
-	public static function onUserMetaDelete(array $meta_ids, int $user_id, string $meta_key, mixed $meta_value):void
-	{
-		if (!str_starts_with($meta_key, BASE)) {
-			return;
-		}
-
-		$user = get_userdata($user_id);
-		if (!$user) {
-			return;
-		}
-
-		self::onUserUpdate($user_id, null);
-	}
-}
diff --git a/inc/managers/CustomTable.php b/inc/managers/CustomTable.php
index 893e5ad..4299b71 100644
--- a/inc/managers/CustomTable.php
+++ b/inc/managers/CustomTable.php
@@ -21,11 +21,21 @@
 {
 	protected \wpdb $wpdb;
 	protected string $tableName;
+	protected string $definition;
 	protected string $fullTableName;
 	protected bool $useTransactions;
+	protected array $columns;
+	protected array $keys = [];
+	protected array $constraints =[];
 
 	/** @var array<string, self> Instance cache for fluent interface */
 	protected static array $instances = [];
+	protected static string $charsetCollate;
+
+	protected static string $userTable;
+	protected static string $userIDType;
+	protected static string $termIDType;
+	protected static string $postIDType;
 
 	/**
 	 * Fluent factory method
@@ -41,9 +51,167 @@
 			self::$instances[$tableName] = new self($tableName);
 		}
 
+
 		return self::$instances[$tableName];
 	}
 
+	public function ensureDefined() {
+		$this->ensureUserTable();
+		$this->ensureUserIDType();
+		$this->ensureTermIDType();
+		$this->ensurePostIDType();
+	}
+		protected function ensureUserTable():void
+		{
+			if (!isset(static::$userTable)) {
+				static::$userTable = is_multisite() ? $this->getMultisiteUsersTable() : $this->wpdb->users;
+			}
+		}
+			protected function getMultisiteUsersTable():string
+			{
+				$siteUsersTable = $this->wpdb->prefix . 'users';
+				$siteExists = $this->wpdb->get_var(
+					$this->wpdb->prepare("SHOW TABLES LIKE %s", $siteUsersTable)
+				);
+				if ($siteExists) {
+					return $siteUsersTable;
+				}
+				//fallback to main one
+				return $this->wpdb->users;
+			}
+		protected function ensureUserIDType():void
+		{
+			if (!isset(static::$userIDType)) {
+				$this->ensureUserTable();
+				static::$userIDType = $this->getColumnType(static::$userTable, 'ID');
+			}
+		}
+
+		protected function ensureTermIDType():void
+		{
+			if (!isset(static::$termIDType)) {
+				static::$termIDType = $this->getColumnType($this->wpdb->terms, 'term_id');
+			}
+		}
+
+		protected function ensurePostIDType():void
+		{
+			if (!isset(static::$postIDType)) {
+				static::$postIDType = $this->getColumnType($this->wpdb->posts, 'ID');
+			}
+		}
+
+	/**
+	 *
+	 * @param array $columns An array of $columnName => $columnDefinition
+	 * @return $this
+	 */
+	public function setColumns(array $columns):self
+	{
+		$this->columns = $columns;
+		return $this;
+	}
+
+	/**
+	 * @param array $keys An array of {string} $keys. If a $key is an array, you can define a custom $key => $value, example: 'UNIQUE' => $value, or 'PRIMARY' => $value
+	 * @return $this
+	 */
+	public function setKeys(array $keys):self
+	{
+		$this->keys = $keys;
+		return $this;
+	}
+
+	/**
+	 * @param array $constraints an array of arrays, each value a $constraint => $references
+	 * @return $this
+	 */
+	public function setConstraints(array $constraints):self
+	{
+		$this->constraints = $constraints;
+		return $this;
+	}
+
+	public function defineTable(): self
+	{
+		if (empty($this->columns)) {
+			error_log('[CustomTable] No columns defined for ' . $this->tableName);
+			return $this;
+		}
+
+		$parts = [];
+
+		// Columns
+		foreach ($this->columns as $name => $type) {
+			$parts[] = "`{$name}` {$type}";
+		}
+
+		// Keys
+		if (empty($this->keys)) {
+			$parts[] = 'PRIMARY KEY (`' . array_key_first($this->columns) . '`)';
+		} else {
+			foreach ($this->keys as $key) {
+				if (is_array($key)) {
+					$value = $key['value'];
+					// Ensure value is wrapped in parentheses
+					if (!str_starts_with(trim($value), '(')) {
+						$value = '(`' . $value . '`)';
+					}
+					$parts[] = $key['key'] . ' KEY ' . $value;
+				} else {
+					$parts[] = 'KEY ' . $key;
+				}
+			}
+		}
+
+		// Constraints
+		foreach ($this->constraints as $constraint => $references) {
+			$parts[] = "CONSTRAINT {$constraint} REFERENCES {$references}";
+		}
+
+		$this->definition = "(\n    " . implode(",\n    ", $parts) . "\n)";
+		return $this;
+	}
+
+	public static function ensureTables():void
+	{
+		require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
+
+		foreach (self::$instances as $instance) {
+			if (!$instance->wpdb->get_var("SHOW TABLES LIKE '{$instance->fullTableName}'")) {
+				$instance->createTable();
+			}
+		}
+	}
+
+	protected function createTable():void
+	{
+		if (!$this->definition && empty($this->definition)) {
+			error_log('[CustomTable]No definition set for '.$this->tableName);
+			return;
+		}
+		$charset = self::$charsetCollate;
+		$schema = "CREATE TABLE IF NOT EXISTS {$this->fullTableName} {$this->definition} {$charset}";
+		$this->wpdb->flush();
+
+		$hasForeignKey = stripos($schema, 'FOREIGN KEY') !== false;
+		if ($hasForeignKey) {
+			$result = $this->wpdb->query($schema);
+			$success = ($result !== false && !$this->wpdb->last_error);
+		} else {
+			dbDelta($schema.';');
+			$success = !$this->wpdb->last_error;
+		}
+
+		if (!$success) {
+			$error_msg = "SQL Error creating table {$this->tableName}: " . $this->wpdb->last_error;
+			error_log($error_msg);
+			error_log("Failed SQL Query: " . $schema);
+		} elseif ($this->wpdb->get_var("SHOW TABLES LIKE '{$this->fullTableName}'")) {
+			error_log("Successfully created table: {$this->fullTableName}");
+		}
+	}
+
 	/**
 	 * Clear instance cache (useful for testing)
 	 */
@@ -61,8 +229,13 @@
 		global $wpdb;
 		$this->wpdb = $wpdb;
 		$this->tableName = $tableName;
-		$this->fullTableName = $wpdb->prefix . BASE . $tableName;
+		$this->fullTableName = $wpdb->prefix . apply_filters('jvb_base', BASE) . $tableName;
 		$this->useTransactions = $useTransactions;
+
+		$usersStatus = $this->wpdb->get_row("SHOW TABLE STATUS LIKE '{$this->wpdb->users}'");
+		$parentCollation = $usersStatus->Collation ?? 'utf8mb4_general_ci';
+		self::$charsetCollate = "DEFAULT CHARACTER SET utf8mb4 COLLATE {$parentCollation}";
+
 	}
 
 	// =========================================================================
@@ -678,10 +851,61 @@
 		return $this->wpdb->rows_affected;
 	}
 
+	public function getUserIDType():string
+	{
+		$this->ensureUserIdType();
+		return static::$userIDType;
+	}
+	public function getUserTable():string
+	{
+		$this->ensureUserTable();
+		return static::$userTable;
+	}
+	public function getTermIDType():string
+	{
+		$this->ensureTermIDType();
+		return static::$termIDType;
+	}
+	public function getPostIDType():string
+	{
+		$this->ensurePostIDType();
+		return static::$postIDType;
+	}
+
 	// =========================================================================
 	// PRIVATE HELPERS
 	// =========================================================================
+	private function getColumnType(string $table, string $column):string|false {
+		$tableExists = $this->wpdb->get_var(
+			$this->wpdb->prepare("SHOW TABLES LIKE %s", $table)
+		);
+		if (!$tableExists) {
+			error_log("[CustomTable] Table {$table} does not exist for getColumnType");
+			return 'bigint(20) unsigned'; // safe fallback
+		}
 
+		$result = $this->wpdb->get_row(
+			$this->wpdb->prepare(
+				"SELECT COLUMN_TYPE
+            FROM INFORMATION_SCHEMA.COLUMNS
+            WHERE TABLE_SCHEMA = DATABASE()
+            AND TABLE_NAME = %s
+            AND COLUMN_NAME = %s",
+				$table,
+				$column
+			)
+		);
+		if ($result && isset($result->COLUMN_TYPE)) {
+			return $result->COLUMN_TYPE;
+		}
+		error_log("[CustomTable] Could not determine column type for {$table}.{$column}");
+		return 'bigint(20) unsigned';
+	}
+
+	private function tableExists():bool
+	{
+		return !is_null($this->wpdb->get_var($this->wpdb->prepare("SHOW TABLES LIKE %s", $this->fullTableName)));
+	}
 	/**
 	 * Build WHERE clause from associative array
 	 */
diff --git a/inc/managers/DashboardManager.php b/inc/managers/DashboardManager.php
index 49a3762..1b5cb1c 100644
--- a/inc/managers/DashboardManager.php
+++ b/inc/managers/DashboardManager.php
@@ -5,7 +5,7 @@
 use JVBase\managers\CRUD;
 use JVBase\meta\Form;
 use JVBase\meta\Meta;
-use JVBase\utility\Features;
+use JVBase\registrar\Registrar;use JVBase\utility\Features;
 use JVBase\ui\Navigation;
 use WP_User;
 
@@ -139,14 +139,14 @@
 	}
 
 
-	protected function getConfig(string $page):array
+	protected function getConfig(string $page):Registrar|false
 	{
 		$pages = $this->getAllDashboardPages();
 		$key = array_search($page, $pages);
 		if ($key === false || is_numeric($key)) {
-			return [];
+			return false;
 		}
-		return Features::getConfig($key);
+		return Registrar::getInstance($key)??false;
 	}
 
 	/**
@@ -166,9 +166,9 @@
 
 	protected function getTitle(string $slug):string
 	{
-		$config = $this->getConfig($slug);
-		if (!empty($config)) {
-			return $config['dash_title']??$config['plural'];
+		$registrar = $this->getConfig($slug);
+		if ($registrar) {
+			return $registrar->getConfig('dashboard')['title']??$registrar->getPlural();
 		}
 		return ucwords(str_replace('-', ' ', str_replace('_', ' ', $slug)));
 	}
@@ -292,9 +292,9 @@
 
     protected function getDescription(string $page):string
     {
-		$config = $this->getConfig($page);
-        if (!empty($config)) {
-            $description = (array_key_exists('dash_description', $config)) ? $config['dash_description'] : '';
+		$registrar = $this->getConfig($page);
+        if ($registrar) {
+            $description =  $registrar->getConfig('dashboard')['description']??'';
         } else {
 			$description = apply_filters('jvbDashboardDescription', $page);
             switch ($page) {
@@ -342,10 +342,10 @@
 
         // Get current page/section
         $page = $this->getCurrentPageTitle();
-		$config = $this->getConfig($page);
-		if(!empty($config)) {
-			add_filter('jvbLoadingIcon', function() use ($config) {
-				return $config['icon'];
+		$registrar = $this->getConfig($page);
+		if($registrar) {
+			add_filter('jvbLoadingIcon', function() use ($registrar) {
+				return $registrar->getIcon();
 			});
 		}
 		$integrationSlugs = array_map(function($name) {
@@ -383,6 +383,7 @@
 		$this->renderHeader();
 		// Pass to page handler
 		$constantSlug = $this->getConstantSlug($page);
+
         echo apply_filters(
 			'jvbDashboardPage',
 			$this->renderPage($page),
@@ -621,10 +622,11 @@
 		return $this->cache->remember('icon_'.sanitize_title($page), function() use ($slug, $page) {
 			$icon = sanitize_title($page);
 			if (!is_numeric($slug)) {
-				$config = Features::getConfig($slug);
-				if (array_key_exists('icon', $config)) {
-					$icon = $config['icon'];
+				$registrar =Registrar::getInstance($slug);
+				if ($registrar) {
+				return $registrar->getIcon();
 				}
+
 			}
 			return $icon;
 		});
@@ -743,7 +745,7 @@
 			//content types
 				//Taxonomies
 		$availableContent = array_filter($pages, function($page, $key) {
-			return !is_numeric($key) && array_key_exists($key, JVB_CONTENT);
+			return !is_numeric($key) && array_key_exists($key, Registrar::getRegistered('post'));
 		}, ARRAY_FILTER_USE_BOTH);
 		if (!empty ($availableContent)){
 			$content = $menu->addItem('Your Content', jvbDashIcon('book-bookmark'))
@@ -751,18 +753,18 @@
 				->defaultMenuClasses($menuClasses)
 				->defaultItemClasses($itemClasses);
 			foreach ($availableContent as $slug => $page) {
-				$config = JVB_CONTENT[$slug];
-				$item = $content->addItem($page, jvbDashIcon($config['icon']))
+				$registrar = Registrar::getInstance($slug);
+
+				$item = $content->addItem($page, $registrar->getIcon())
 					->url($this->baseURL.'/'.$slug);
 
-				$taxonomies = array_filter(JVB_TAXONOMY, function ($value, $key) use ($slug) {
-					return in_array($slug, $value['for_content']);
-				},1);
+				$taxonomies = $registrar->registrar->taxonomies;
 				if (!empty ($taxonomies)) {
 					//TODO: If we add a dedicated 'create item' page, remove this from the empty check
 					$itemMenu = $item->submenu($slug);
-					foreach ($taxonomies as $s => $config) {
-						$itemMenu->addItem($config['plural'], $config['icon'])
+					foreach ($taxonomies as $s) {
+						$taxRegistrar = Registrar::getInstance($s);
+						$itemMenu->addItem($taxRegistrar->getPlural(), $taxRegistrar->getIcon())
 							->url($this->baseURL.'/'.$s);
 					}
 				}
@@ -971,7 +973,7 @@
 		$connection = (array_key_exists($page, $map)) ? $map[$page] : $page;
 		if ($connection !== 'integrations') {
 
-			$userID = (jvbSiteHasMembership()) ? $this->user->ID : null;
+			$userID = (Features::forSite()->has('has_membership')) ? $this->user->ID : null;
 			$integration = JVB()->connect($connection, $userID);
 
 			echo '<h1>Managing '.$integration->title.'</h1>';
@@ -1078,20 +1080,19 @@
         <nav class="tabs row start" role="tablist">
         <?php
         $i=1;
-        $content = JVB_CONTENT;
-        $contentTax = array_filter(JVB_TAXONOMY, function ($tax) {
-            return jvbCheck('is_content', $tax);
-        });
-        $taxonomies = JVB_TAXONOMY;
-        foreach($contentTax as $key => $config) {
-            unset($taxonomies[$key]);
+        $content = Registrar::getRegistered('post');
+        $contentTax = Registrar::getFeatured('is_content', 'term');
+        $taxonomies = Registrar::getRegistered('term');
+        foreach($contentTax as $index => $tax) {
+            unset($taxonomies[$index]);
         }
         $content = array_merge($content, $contentTax);
-        foreach ($content as $type => $settings) {
+        foreach ($content as $type) {
+			$registrar = Registrar::getInstance($type);
             $active = ($i === 1) ? ' active' : '';
             ?>
             <button type="button" class="tab<?=$active?>" data-tab="<?=$type?>" role="tab" aria-selected="<?= ($active !== '') ? 'true' : 'false'?>">
-                <h2><?=jvbDashIcon($settings['icon']??$key)?> <?= $settings['plural'] ?></h2>
+                <h2><?=jvbDashIcon($registrar->getIcon())?> <?= $registrar->getPlural() ?></h2>
             </button>
             <?php
             $i++;
@@ -1102,8 +1103,9 @@
             <option> ... Taxonomy</option>
             <?php
 
-            foreach ($taxonomies as $type => $settings) {
-                echo '<option value="'.$type.'">'.$settings['plural'].'</option>';
+            foreach ($taxonomies as $type) {
+				$taxRegistrar = Registrar::getInstance($type);
+                echo '<option value="'.$type.'">'.$taxRegistrar->getPlural().'</option>';
             }
             ?>
         </select>
@@ -1127,10 +1129,10 @@
 
     <?php
 
-    $jvb_everything = array_merge(JVB_CONTENT, JVB_TAXONOMY);
 
-    foreach ($jvb_everything as $type => $settings) {
-        $fields = jvbGetFields($type);
+
+    foreach (Registrar::getRegistered() as $type) {
+        $fields = Registrar::getFieldsFor($type);
         ?>
         <template class="<?= $type ?>Table">
             <table>
@@ -1272,7 +1274,7 @@
 				$pages[] = 'Favourites';
 			}
 
-			if (Features::anyContentHas('karma') || Features::anyTaxonomyHas('karma') || Features::anyUserHas('karma')) {
+			if (!empty(Registrar::getFeatured('karma'))) {
 				$pages[] = 'Karmic Score';
 			}
 
@@ -1289,12 +1291,14 @@
 			}
 
 			// Add all content types (with config keys)
-			foreach (JVB_CONTENT as $slug => $config) {
-				$pages[$slug] = $config['plural'];
+			foreach (Registrar::getRegistered('post') as $slug) {
+				$registrar = Registrar::getInstance($slug);
+				$pages[$slug] = $registrar->getPlural();
 			}
 
-			foreach (JVB_TAXONOMY as $slug=>$config) {
-				$pages[$slug] = $config['plural'];
+			foreach (Registrar::getRegistered('term') as $slug) {
+				$registrar = Registrar::getInstance($slug);
+				$pages[$slug] = $registrar->getPlural();
 			}
 
 			// Allow filtering
@@ -1321,7 +1325,7 @@
 	{
 		$role = jvbNoBase($role);
 
-		if (!array_key_exists($role, JVB_USER)) {
+		if (!Registrar::getInstance($role)){
 			return [];
 		}
 
@@ -1366,21 +1370,22 @@
 				//Default to Remove pages
 				$remove = true;
 				if (!is_numeric($key)) {
-					$type  = Features::getType($key);
+					$registrar = Registrar::getInstance($key);
+					$type  = $registrar? $registrar->getType() : false;
 					if ($type) {
-						$permission = RoleManager::getPlural($key);
+						$permission = JVB()->roles()->getPermission('edit', $key);
 					}
 					switch ($type) {
 						case 'content':
-							if (user_can($userID, "edit_{$permission}")) {
+							if (user_can($userID, $permission)) {
 								$remove = false;
 							}
 							break;
 						case 'taxonomy':
-							$config = Features::getConfig($key, 'taxonomy');
-							if (array_key_exists('is_content', $config) && $config['is_content'] && (user_can($userID, "own_{$key}") || user_can($userID, "manage_{$key}"))) {
+							$registrar = Registrar::getInstance($key);
+							if ($registrar && $registrar->hasFeature('is_content') && (!empty(JVB()->roles()->getOwnedTerms($userID, $key)) || !empty(JVB()->roles()->getManagedTerms($userID, $key)))){
 								$remove = false;
-							} else if (count(array_intersect($config['for_content'], array_keys($pages))) > 0) {
+							} else if (count(array_intersect($registrar->registrar->for, array_keys($pages))) > 0) {
 								$remove = false;
 							}
 							break;
@@ -1402,7 +1407,8 @@
 								}
 							}
 							if ($remove) {
-								if ($canSkip || array_key_exists('invitable', $config)) {
+								//TODO: Figure out what $config was supposed to be
+								if ($canSkip || ($registrar && $registrar->hasFeature('invitable'))) {
 									$remove = false;
 								}
 							}
@@ -1449,11 +1455,8 @@
 							foreach ($roles as $role) {
 								$contents = Features::forUser($role)->getCreatableContent();
 								if (!empty($contents)) {
-									foreach($contents as $content) {
-										if (Features::forContent($content)->has('karma')) {
-											$remove = false;
-										}
-									}
+									$hasKarma = Registrar::getFeatured('karma');
+									$remove = empty(array_intersect($contents, $hasKarma));
 								}
 							}
 							break;
@@ -1513,9 +1516,7 @@
 	 */
 	protected function getRolesWithDashboard():array
 	{
-		return array_keys(array_filter(JVB_USER, function ($role) {
-			return Features::forUser(array_search($role, JVB_USER))->has('has_dashboard');
-		}));
+		return Registrar::getFeatured('has_dashboard', 'user');
 	}
 
 	/**
@@ -1535,20 +1536,4 @@
 		return count(array_intersect($dashboardRoles, $userRoles)) > 0;
 	}
 
-	/**
-	 * Get the capability needed to access a content type
-	 * @param string $type
-	 * @return string
-	 */
-	protected function getPermissionForType(string $type):string
-	{
-		// Check if it's a registered content type
-		if (array_key_exists($type, JVB_CONTENT)) {
-			$plural = JVB_CONTENT[$type]['plural'];
-			return 'edit_'.$plural;
-		}
-
-		// Default to edit_{type}s
-		return 'edit_'.$type.'s';
-	}
 }
diff --git a/inc/managers/DirectoryManager.php b/inc/managers/DirectoryManager.php
index f8048b6..46f4ef8 100644
--- a/inc/managers/DirectoryManager.php
+++ b/inc/managers/DirectoryManager.php
@@ -5,9 +5,7 @@
 	exit;
 }
 
-use JVBase\registry\PostTypeRegistrar;
-use JVBase\managers\Cache;
-use JVBase\utility\Features;
+use JVBase\registrar\Registrar;
 use WP_Block;
 use WP_Query;
 
@@ -37,12 +35,14 @@
 			$this->cache->flush();
 		}
 
+		jvb_register_do_once('buildDirectories', [$this, 'activate']);
 		add_action('init', [$this, 'registerDirectories']);
         add_action('render_block', [$this, 'renderBlock'], 99999, 3);
     }
 
     public function registerDirectories():void
     {
+
 		$singular = (array_key_exists('directory_label', JVB_SITE)) ? JVB_SITE['directory_label'][0] : 'Directory';
 		$plural = (array_key_exists('directory_label', JVB_SITE)) ? JVB_SITE['directory_label'][1] : 'Directories';
 		$config = [
@@ -87,25 +87,25 @@
 		if (!$directories) {
 			$directories = [];
 			//content
-			if(Features::anyContentHas('show_directory')) {
-				foreach (JVB_CONTENT as $key => $config) {
-					if (Features::forContent($key)->has('show_directory')) {
-						$directories[$key] = 'content';
-					}
+
+			$content = Registrar::getFeatured('show_directory', 'post');
+			if(!empty($content)) {
+				foreach ($content as $key) {
+					$directories[$key] = 'content';
 				}
 			}
-			if(Features::anyTaxonomyHas('show_directory')) {
-				foreach (JVB_TAXONOMY as $key=>$config) {
-					if (Features::forTaxonomy($key)->has('show_directory')) {
-						$directories[$key] = 'taxonomy';
-					}
+
+			$taxonomies = Registrar::getFeatured('show_directory', 'term');
+			if(!empty($taxonomies)) {
+				foreach ($taxonomies as $key) {
+					$directories[$key] = 'taxonomy';
 				}
 			}
-			if (Features::anyUserHas('show_directory')) {
-				foreach(JVB_USER as $key=>$config) {
-					if (Features::forUser($key)->has('show_directory')) {
-						$directories[$key] = 'user';
-					}
+
+			$users = Registrar::getFeatured('show_directory', 'user');
+			if(!empty($users)) {
+				foreach ($users as $key) {
+					$directories[$key] = 'user';
 				}
 			}
 
@@ -113,34 +113,28 @@
 		}
 		return $directories;
 	}
-	protected function getConfigFromType(string $type):array
-	{
-		if (!array_key_exists($type, $this->directories)) {
-			return [];
-		}
-		return match ($this->directories[$type]) {
-			'content' => JVB_CONTENT[$type],
-			'taxonomy' => JVB_TAXONOMY[$type],
-			'user' => JVB_USER[$type],
-			default => [],
-		};
 
-	}
-
-    public static function activate()
+    public function activate():void
     {
-		$tmp = new self();
-		$tmp->registerDirectories();
+//		$tmp = new self();
+//		$this->registerDirectories();
 
         $created = [];
         $directories = [];
+		$this->getDirectories();
+		foreach($this->directories as $directory => $type) {
+			$registrar = Registrar::getInstance($directory);
+			if (!$registrar){
+				error_log('Could not find registrar for making directory for '.$directory);
+				continue;
+			}
 
-		foreach($tmp->directories as $directory => $type) {
-			$config = $tmp->getConfigFromType($directory);
-			$title = $tmp->directoryTitle($config);
+			$config = $registrar->getConfig('directory');
+
+			$title = $config['title'];
 			$excerpt = implode(' ', $config['description']??[]);
 			$ID = wp_insert_post([
-				'post_type'	=> BASE.'directory',
+				'post_type'		=> BASE.'directory',
 				'post_title'	=> $title,
 				'post_status'	=> 'publish',
 				'post_excerpt'	=> $excerpt,
@@ -162,13 +156,7 @@
 					'extra'    => $config[$directory]['directory_extra'] ??[],
 				];
 			}
-			$isGrouped = match ($type) {
-				'content' => Features::forContent($directory)->has('isGrouped'),
-				'taxonomy' => Features::forTaxonomy($directory)->has('isGrouped'),
-				'user' => Features::forUser($directory)->has('isGrouped'),
-				default => false,
-			};
-			if ($isGrouped) {
+			if ($config['isGrouped']) {
 				$title = $title.', but Grouped';
 				$slug = sanitize_title($title).'-grouped';
 				$excerpt = $config['groupedDescription']??'Too many options? This is grouped by type.';
@@ -237,15 +225,17 @@
 				'posts_per_page'	=> -1,
 			]);
 			foreach($all->posts as $post) {
+				$config = Registrar::getInstance($post->post_name)->getConfig('directory')??false;
+
 				$saved[$post->post_name] = [
 					'slug'	=> $post->post_name,
 					'title'	=> $post->post_title,
 					'ID'	=> $post->ID,
 					'url'	=> get_the_permalink($post->ID),
 					'page'	=> $post->post_title,
-					'description'	=> $this->getConfigFromType($post->post_name)['description']??'',
+					'description'	=> ($config) ?$config['description'] :'',
 					'type'	=> get_post_meta($post->ID, self::$type,true),
-					'extra'	=> $this->getConfigFromType($post->post_name)['directory_extra']??[],
+					'extra'	=> ($config) ?$config['directory_extra'] : [],
 				];
 			}
 			update_option(BASE.'directory_list', $saved);
@@ -269,16 +259,10 @@
 		return $this->directoryList;
 	}
 
-    public static function getConfig(int $ID):array
+    public static function getConfig(int $ID):Registrar|false
     {
-        $type = get_post_meta($ID, self::$type, true);
         $slug = get_post_meta($ID, self::$slug, true);
-		return match ($type) {
-			'content' => JVB_CONTENT[$slug],
-			'taxonomy' => JVB_TAXONOMY[$slug],
-			'user' => JVB_USER[$slug],
-			default => [],
-		};
+		return Registrar::getInstance($slug);
 	}
 
     public function letters():array
@@ -342,7 +326,7 @@
 
 	public function directories(string $search = 'all'):array
 	{
-		$directories = $this->getDirectories();
+		$directories = $this->getDirectoryList();
 		if ($search === 'all') {
 			return $directories;
 		}
@@ -364,11 +348,11 @@
                 <p>You like lists? We\'ve got \'em!</p>
                 <ul class="directories">';
 				foreach ($this->directoryList as $slug => $directory) {
-					$config = $this->getConfigFromType($slug);
+					$config = Registrar::getInstance($slug);
 					$aOpen = '<a href="'.$directory['url'].'" title="See our list of '.$directory['title'].'">';
 					$aClose = '</a>';
 					$cache .= '<li class="directory col start">
-						'. $aOpen.jvbIcon(array_key_exists('icon', $config) ? $config['icon']:'list-dashes').$directory['title'].$aClose;
+						'. $aOpen.jvbIcon($config->getIcon() !== '' ? $config->getIcon() :'list-dashes').$directory['title'].$aClose;
 					if (!empty($directory['description'])) {
 						$cache .= '<div class="description">';
 						foreach ($directory['description'] as $description) {
@@ -392,8 +376,8 @@
 				$cache = '<nav class="directory condensed"><ul>';
 				foreach ($this->getDirectoryList() as $slug => $directory) {
 					$actualSlug = str_replace('-grouped', '', $slug);
-					$config = $this->getConfigFromType($actualSlug);
-					$icon = jvbIcon($config['icon']??'');
+					$config = Registrar::getInstance($actualSlug);
+					$icon = $config->getIcon() !== '' ? jvbIcon($config->getIcon()) : '';
 					$cache .= '<li id="'.$slug.'">
                     <a href="'.$directory['url'].'" class="'.$actualSlug.'">'.
 						$icon.$directory['title'].'
@@ -421,7 +405,8 @@
 	}
 
 	$type = $this->directories[$slug];
-	$config = $this->getConfigFromType($slug);
+	$registrar = Registrar::getInstance($slug);
+	$config = $registrar->getConfig('directory');
 
 	$paged = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1;
 
@@ -429,10 +414,10 @@
 
 	return $this->cache->remember(
 		$cacheKey,
-		function() use ($slug, $type, $config, $paged) {
-			$out = '<h1>' . $this->directoryTitle($config) . '</h1>';
+		function() use ($slug, $type, $registrar, $config, $paged) {
+			$out = '<h1>' . $this->directoryTitle($registrar) . '</h1>';
 			$out .= '<div class="description">';
-			foreach ($config[$slug]['description'] ?? [] as $p) {
+			foreach ($config['description'] ?? [] as $p) {
 				$out .= '<p>' . $p . '</p>';
 			}
 			$out .= '</div>';
@@ -451,13 +436,14 @@
 						'order' => 'ASC'
 					];
 
-					if (Features::forContent($slug)->has('is_timeline')) {
+
+					if ($registrar->hasFeature('is_timeline')) {
 						$args['post_parent'] = 0;
 					}
 
 					$get = new WP_Query($args);
 
-						$hasExtra = Features::forContent($slug)->has('directory_extra');
+						$hasExtra = $registrar->hasFeature('directory_extra');
 						if ($get->have_posts()) {
 							while ( $get->have_posts() ) {
 								$get->the_post();
@@ -716,9 +702,10 @@
         return $content;
     }
 
-	protected function directoryTitle(array $config):string
+	protected function directoryTitle(Registrar $registrar):string
 	{
-		return array_key_exists('directory', $config) ? $config['directory'] : $config['plural'];
+		$config = $registrar->getConfig('directory');
+		return $config['title']?: $registrar->getPlural();
 	}
 
 	public function referAs($plural = false):string
@@ -827,14 +814,14 @@
 			$slug . '_letter_page_map',
 			function() use ($type, $slug) {
 				$titles = [];
-
+				$registrar = Registrar::getInstance($slug);
 				switch ($type) {
 					case 'content':
 						global $wpdb;
 						$post_type = jvbCheckBase($slug);
 
 						$where = $wpdb->prepare("post_type = %s AND post_status = 'publish'", $post_type);
-						if (Features::forContent($slug)->has('is_timeline')) {
+						if ($registrar && $registrar->hasFeature('is_timeline')) {
 							$where .= " AND post_parent = 0";
 						}
 
diff --git a/inc/managers/IconsManager.php b/inc/managers/IconsManager.php
index 65e583d..3e12e60 100644
--- a/inc/managers/IconsManager.php
+++ b/inc/managers/IconsManager.php
@@ -1,6 +1,7 @@
 <?php
 namespace JVBase\managers;
 
+use JVBase\registrar\Registrar;
 use JVBase\utility\Features;
 
 if (!defined('ABSPATH')) {
@@ -198,12 +199,7 @@
 		// Add icons from content/taxonomy/user configs (like old behavior)
 		$configIcons = $this->getIconsFromConfigs();
 		if (!empty($configIcons)) {
-			foreach ($configIcons as $source => $icons) {
-				if (!isset($defaults[$source])) {
-					$defaults[$source] = [];
-				}
-				$defaults[$source] = array_merge($defaults[$source], $icons);
-			}
+			$defaults['icons'] = array_merge($defaults['icons'], $configIcons);
 		}
 
 		// Allow filtering per source (extensibility)
@@ -221,28 +217,21 @@
 	}
 
 	/**
-	 * Get icons from JVB_CONTENT, JVB_TAXONOMY, JVB_USER configs
+	 * Get icons from Registrar instances
+	 *
 	 */
 	protected function getIconsFromConfigs(): array
 	{
 		$icons = [];
-		$check = [JVB_CONTENT, JVB_TAXONOMY, JVB_USER];
+		$registered = Registrar::getRegistered();
 
-		foreach ($check as $constant) {
-			foreach ($constant as $key => $value) {
-				if (isset($value['icon'])) {
-					// Determine source based on context (you could add 'icon_source' to configs)
-					$source = $value['icon_source'] ?? 'icons';
-
-					if (!isset($icons[$source])) {
-						$icons[$source] = [];
-					}
-					$icons[$source][] = $value['icon'];
-				}
-			}
+		foreach ($registered as $type) {
+			$registrar = Registrar::getInstance($type);
+			$icons[] = $registrar->getIcon();
 		}
 
-		return $icons;
+
+		return array_unique(array_filter($icons));
 	}
 
 	/**
diff --git a/inc/managers/InvitationsManager.php b/inc/managers/InvitationsManager.php
index ebf3c92..088556f 100644
--- a/inc/managers/InvitationsManager.php
+++ b/inc/managers/InvitationsManager.php
@@ -4,6 +4,7 @@
 use Exception;
 use JVBase\managers\queue\executors\InvitationExecutor;
 use JVBase\managers\queue\TypeConfig;
+use JVBase\registrar\Registrar;
 use JVBase\utility\Features;
 use WP_Error;
 
@@ -16,9 +17,11 @@
 	protected array $inviteConfig;
 	protected CustomTable $table;
 	protected int $expiryDays = 14;
+	protected Cache $cache;
 	public function __construct()
 	{
 		$this->setInviteConfig();
+		$this->cache = Cache::for('invitations');
 		$this->table = CustomTable::for('invitations');
 		add_action('init', [$this, 'registerInvitationExecutors'], 5);
 
@@ -86,46 +89,58 @@
 	 */
 	protected function buildInviteTypes(): array
 	{
-		$types = [];
+		$invitable = $this->cache->remember(
+			'invitableTypes',
+			function () {
+				$types = [];
 
-		// Global invitations from JVB_MEMBERSHIP
-		if (!empty(JVB_MEMBERSHIP['can_invite'])) {
-			foreach (JVB_MEMBERSHIP['can_invite'] as $role => $canInvite) {
-				$types[$role] = [
-					'can_invite' => $canInvite,
-					'to_terms' => []
-				];
-			}
-		}
+				// Global invitations from JVB_MEMBERSHIP
+				if (!empty(JVB_MEMBERSHIP['can_invite'])) {
+					foreach (JVB_MEMBERSHIP['can_invite'] as $role => $canInvite) {
+						$types[$role] = [
+							'can_invite' => $canInvite,
+							'to_terms' => []
+						];
+					}
+				}
 
-		// Term invitations from invitable content taxonomies
-		foreach (JVB_TAXONOMY as $taxonomy => $config) {
-			if (Features::forTaxonomy($taxonomy)->has('invitable') &&
-				Features::forTaxonomy($taxonomy)->has('is_content') &&
-				Features::forTaxonomy($taxonomy)->has('is_ownable')) {
+				// Term invitations from invitable content taxonomies
+				$invitable = Registrar::getFeatured('invitable', 'term');
+				$content = Registrar::getFeatured('is_content', 'term');
+				$ownable = Registrar::getFeatured('is_ownable', 'term');
+				$taxonomies = array_intersect($invitable, $content, $ownable);
+				if (!empty($taxonomies)) {
+					$users = Registrar::getRegistered('user');
+				}
+				foreach ($taxonomies as $taxonomy) {
+					$registrar = Registrar::getInstance($taxonomy);
 
-				$forContent = $config['for_content'] ?? [];
-				foreach ($forContent as $content) {
-					// Find which user roles can create this content
-					foreach (JVB_USER as $role => $userConfig) {
-						$creatable = Features::forUser($role)->getCreatableContent();
-						if (in_array($content, $creatable)) {
-							if (!isset($types[$role])) {
-								$types[$role] = [
-									'can_invite' => [],
-									'to_terms' => []
-								];
-							}
-							if (!in_array($taxonomy, $types[$role]['to_terms'])) {
-								$types[$role]['to_terms'][] = $taxonomy;
+					foreach ($registrar->registrar->for as $content) {
+						// Find which user roles can create this content
+						foreach ($users as $user) {
+							$userRegistrar = Registrar::getInstance($user);
+
+							$creatable = $userRegistrar->getCreatable();
+							if (in_array($content, $creatable)) {
+								if (!isset($types[$role])) {
+									$types[$role] = [
+										'can_invite' => [],
+										'to_terms' => []
+									];
+								}
+								if (!in_array($taxonomy, $types[$role]['to_terms'])) {
+									$types[$role]['to_terms'][] = $taxonomy;
+								}
 							}
 						}
 					}
 				}
-			}
-		}
 
-		return $types;
+				return $types;
+			}
+		);
+		return $invitable;
+
 	}
 
 	/******************************************************************
@@ -467,8 +482,9 @@
 
 			$term = get_term($termID, BASE . $taxonomy);
 			if ($term && !is_wp_error($term)) {
-				$config = JVB_TAXONOMY[$taxonomy] ?? [];
-				$singular = $config['singular'] ?? $taxonomy;
+				$registrar = Registrar::getInstance($taxonomy);
+
+				$singular = $registrar ? $registrar->getSingular() : $taxonomy;
 
 				$termContent[] = sprintf(
 					"<p>%s has also invited you to join %s. You'll be automatically added to this %s when you register.</p>",
diff --git a/inc/managers/NotificationManager.php b/inc/managers/NotificationManager.php
index 5a02499..2aaa4cf 100644
--- a/inc/managers/NotificationManager.php
+++ b/inc/managers/NotificationManager.php
@@ -2,6 +2,7 @@
 namespace JVBase\managers;
 
 use JVBase\JVB;
+use JVBase\registrar\Registrar;
 use WP_Error;
 use Exception;
 use WP_Post;
@@ -423,7 +424,7 @@
      */
     public function notifyEveryone(string $type, int|null $action_user_id = null, string $message = '', int|null $target_id = null, string|null $target_type = null, array|null $context = null):bool|WP_Error
     {
-        $artists = $this->getUserIDs(array_keys(JVB_USER));
+        $artists = $this->getUserIDs(Registrar::getRegistered('user'));
         return $this->addNotification($artists, $type, $action_user_id, $message, $target_id, $target_type, $context);
     }
 
@@ -453,7 +454,7 @@
         }
 
         // Check if this is a relevant content type
-        $content_types = jvbBasedFeedContent();
+        $content_types = array_map(function($type) {return jvbCheckBase($type->getSlug()); }, Registrar::getFeatured('show_feed', 'post'));
         if (!in_array($post->post_type, $content_types)) {
             return;
         }
@@ -1617,14 +1618,9 @@
      */
     protected function pluralize(string $content):string
     {
-        if (array_key_exists($content, JVB_CONTENT)) {
-			return JVB_CONTENT[$content]['plural'];
-		} elseif (array_key_exists($content, JVB_TAXONOMY)) {
-			return JVB_TAXONOMY[$content]['plural'];
-		} elseif (array_key_exists($content, JVB_USER)) {
-			return JVB_USER[$content]['plural'];
-		}
-		return $content;
+		$registrar = Registrar::getInstance($content);
+		return ($registrar) ? $registrar->getPlural()
+			: str_replace('_', ' ', $content.'s');
     }
 
     /**
@@ -1749,7 +1745,7 @@
 		return array_map(function ($r) {
 			return jvbCheckBase(trim($r));
 		}, array_filter($roles, function ($r) {
-			return array_key_exists(trim($r), JVB_USER);
+			return Registrar::getInstance(trim($r))!== false;
 		}));
 	}
 
diff --git a/inc/managers/RoleManager.php b/inc/managers/RoleManager.php
index d4a3988..0f235d6 100644
--- a/inc/managers/RoleManager.php
+++ b/inc/managers/RoleManager.php
@@ -1,6 +1,7 @@
 <?php
 namespace JVBase\managers;
 
+use JVBase\registrar\Registrar;
 use JVBase\utility\Features;
 use WP_User;
 use WP_Role;
@@ -15,10 +16,17 @@
 
     public function __construct()
     {
-       $this->roles = array_keys(JVB_USER);
+       $this->roles = array_keys(array_map(function ($instance) {
+		   return $instance->slug;
+	   }, Registrar::getRegistered('user')));
+
 	   $this->content = array_map(function($content) {
-		   return strtolower($content['plural']??$content['singular'].'s');
-	   },JVB_CONTENT);
+		   $registrar = Registrar::getInstance($content);
+		   return strtolower(str_replace(' ', '_', $registrar->getPlural()??$registrar->getSingular().'s'));
+	   },array_merge(
+		   Registrar::getRegistered('post'),
+		   Registrar::getFeatured('is_content', 'term')
+	   ));
 	   add_action('set_user_role', [$this, 'updateRoles'], 10, 3);
     }
 
@@ -28,7 +36,8 @@
 			return;
 		}
 		$temp = jvbNoBase($role);
-		if (array_key_exists($temp, JVB_USER)) {
+		$registrar = Registrar::getInstance($temp);
+		if ($registrar) {
 			$user = get_userdata($userID);
 			if (!$user) {
 				return;
@@ -56,17 +65,13 @@
 	{
 		$type = jvbNoBase($type);
 
-		// Check in JVB_CONTENT array
-		if (array_key_exists($type, JVB_CONTENT)) {
+		$registrar = Registrar::getInstance($type);
+		if ($registrar && $registrar->getType() === 'post') {
 			return true;
 		}
 
-		// Check in JVB_TAXONOMY for content taxonomies
-		if (array_key_exists($type, JVB_TAXONOMY)) {
-			$tax_config = JVB_TAXONOMY[$type];
-			if ($tax_config['is_content'] ?? false) {
-				return true;
-			}
+		if ($registrar && $registrar->getType() === 'term' && $registrar->hasFeature('is_content')) {
+			return true;
 		}
 
 		return false;
@@ -91,11 +96,11 @@
 
 		foreach ($roles as $role) {
 			$role = jvbNoBase($role);
-			$config = JVB_USER[$role]??false;
-			if (!$config) {
+			$registrar = Registrar::getInstance($role);
+			if (!$registrar) {
 				return false;
 			}
-			foreach ($config as $type) {
+			foreach ($registrar->getCreatable() as $type) {
 				if (is_array($type) && in_array($content, $type)) {
 					return true;
 				} elseif ($content === $type) {
@@ -185,15 +190,19 @@
 		if ($check) {
 			return $check;
 		}
-		$check = JVB_USER[$role]['can_create'] ??false;
-		if (!$check) {
+
+		$registrar = Registrar::getInstance($role);
+		if (!$registrar) {
 			return false;
 		}
 		$out = [];
-		foreach ($check as $types) {
-			foreach ($types as $type => $content) {
-				$out[$type] = $content;
+		foreach ($registrar->getCreatable() as $types) {
+			if (is_array($types)) {
+				$out = array_merge($out, $types);
+			} else {
+				$out = [$types];
 			}
+			$out = array_unique($out);
 		}
 		set_transient(BASE.'role_config_'.$role, $out, MONTH_IN_SECONDS);
 		return $out;
@@ -230,13 +239,17 @@
         return $link;
     }
 
-	public function registerRole(string $slug, array $config): void
+	public function registerRole(string $slug): void
 	{
 		$role_name = BASE . $slug;
 		$display_name = $config['label'] ?? ucfirst($slug);
 
+		$registrar = Registrar::getInstance($slug);
+		if (!$registrar){
+			return;
+		}
 		// Build capabilities for this role
-		$capabilities = $this->buildRoleCapabilities($slug, $config);
+		$capabilities = $this->buildRoleCapabilities($slug, $registrar);
 
 		// Remove role first to ensure clean slate
 		remove_role($role_name);
@@ -245,7 +258,7 @@
 		add_role($role_name, $display_name, $capabilities);
 
 		// Add management capabilities to administrator
-		if ($config['has_dashboard'] ?? false) {
+		if ($registrar->hasFeature('has_dashboard') ?? false) {
 			$admin_role = get_role('administrator');
 			if ($admin_role) {
 				$admin_role->add_cap("manage_{$role_name}s", true);
@@ -254,7 +267,7 @@
 		}
 	}
 
-	private function buildRoleCapabilities(string $slug, array $config): array
+	private function buildRoleCapabilities(string $slug, Registrar $registrar): array
 	{
 		//Everyone can see the things
 		$capabilities = [
@@ -262,24 +275,32 @@
 		];
 
 		// Dashboard access
-		if ($this->config['has_dashboard'] ?? false) {
+		if ($registrar->hasFeature('has_dashboard') ?? false) {
 			$capabilities['access_dashboard'] = true;
 		}
 
-		if (Features::forSite()->has('favourites') && $config['can_favourite'] ?? true) {
+		if (Features::forSite()->has('favourites') && $registrar->hasFeature('can_favourite') ?? true) {
 			$capabilities['can_favourite'] = true;
 		}
 
 		// Content creation capabilities
-		if (!empty($config['can_create'])) {
-			foreach ($config['can_create'] as $content_type) {
-				$this->addContentCapabilities($capabilities, $content_type, $config);
+		if (!empty($registrar->getCreatable())) {
+			$content = [];
+			foreach ($registrar->getCreatable() as $content_type) {
+				if (is_array($content_type)) {
+					$content = array_merge($content, $content_type);
+				}else {
+					$content[] = $content_type;
+				}
+			}
+			foreach  ($content as $c) {
+				$this->addContentCapabilities($capabilities, $c, $registrar);
 			}
 		}
 
 		// Management capabilities
-		if (!empty($this->config['manage_others'])) {
-			foreach ($this->config['manage_others'] as $type) {
+		if (!empty($registrar->getManageOthers())) {
+			foreach ($registrar->getManageOthers() as $type) {
 				// Skip if content type doesn't exist
 				if (!$this->isValidContentType($type)) {
 					error_log("Warning: User role '{$slug}' references non-existent content type '{$type}'");
@@ -298,44 +319,30 @@
 	/**
 	 * Add content capabilities to capability array
 	 */
-	private function addContentCapabilities(array &$capabilities, $content_type, array $config): void
+	private function addContentCapabilities(array &$capabilities, $content_type, Registrar $registrar): void
 	{
-		if (is_array($content_type)) {
-			// Handle array format for type-specific permissions
-			foreach ($content_type as $sub_type => $types) {
-				foreach ($types as $type) {
-					if (!$this->isValidContentType($type)) {
-						error_log("Warning: Role references non-existent content type '{$type}'");
-						continue;
-					}
-					$this->addSingleContentCapabilities($capabilities, $type, $config);
-				}
-			}
-		} else {
-			if (!$this->isValidContentType($content_type)) {
-				error_log("Warning: Role references non-existent content type '{$content_type}'");
-				return;
-			}
-			$this->addSingleContentCapabilities($capabilities, $content_type, $config);
+		if (!$this->isValidContentType($content_type)) {
+			error_log("Warning: Role references non-existent content type '{$content_type}'");
+			return;
 		}
+		$this->addSingleContentCapabilities($capabilities, $content_type, $registrar);
 	}
 
 	/**
 	 * Add capabilities for a single content type
 	 */
-	private function addSingleContentCapabilities(array &$capabilities, string $type, array $config): void
+	private function addSingleContentCapabilities(array &$capabilities, string $type, Registrar $registrar): void
 	{
 		$caps = $this->getCapabilities($type);
 		foreach ($caps as $cap) {
 			$capabilities[$cap] = true;
 		}
 
-		if (array_key_exists('approve_new', $config)) {
+		if ($registrar->hasFeature('approve_new')) {
 			$plural = $this->getContentPlural($type);
 			// Publish capability depends on approval setting
 			$capabilities["publish_{$plural}"] = !($config['approve_new'] ?? false);
 		}
-
 	}
 
 	public function grantRoleCapabilities(string $role_name, string $content_slug, bool $grant = true): void
@@ -393,7 +400,6 @@
 			"edit_others_{$plural}",
 			"publish_{$plural}",
 			"read_private_{$plural}",
-			"edit_{$plural}",
 		];
 	}
 	protected function getOthersCapabilities(string $content):array
@@ -420,22 +426,17 @@
 	public function getContentPlural(string $content): string
 	{
 		$content = jvbNoBase($content);
-		$config = Features::getConfig($content);
-		$capsMap = $config['capability_type']??[];
-		if (empty($capsMap)){
-			$capsMap = [
-				$content,
-				str_replace('-', '_',sanitize_title(strtolower(JVB_CONTENT[$content]['plural']??JVB_TAXONOMY[$content]['plural'])))
-			];
-			return $capsMap[1];
+		$registrar = Registrar::getInstance($content);
+		if ($registrar && $registrar->getPlural()) {
+			return str_replace(' ', '_', $registrar->getPlural());
 		}
-		return str_replace('-', '_', sanitize_title(strtolower($content . 's')));
+		return str_replace(' ', '_', $content.'s');
 	}
 
 	public function activate(): void
 	{
-		foreach (JVB_USER as $slug => $config) {
-			$this->registerRole($slug, $config);
+		foreach (Registrar::getRegistered('user') as $role) {
+			$this->registerRole($role);
 		}
 	}
 
@@ -458,9 +459,10 @@
 		}
 		$taxonomy = jvbNoBase($taxonomy);
 
+		$registrar = Registrar::getInstance($taxonomy);
 		// Verify this is an ownable content taxonomy
-		if (!Features::forTaxonomy($taxonomy)->has('is_content') ||
-			!Features::forTaxonomy($taxonomy)->has('is_ownable')) {
+		if (!$registrar || !$registrar->hasFeature('is_content') ||
+				!$registrar->hasFeature('is_ownable')) {
 			return false;
 		}
 
@@ -521,9 +523,10 @@
 		}
 		$taxonomy = jvbNoBase($taxonomy);
 
+		$registrar = Registrar::getInstance($taxonomy);
 		// Verify this is an ownable content taxonomy
-		if (!Features::forTaxonomy($taxonomy)->has('is_content') ||
-			!Features::forTaxonomy($taxonomy)->has('is_ownable')) {
+		if (!$registrar || !$registrar->hasFeature('is_content') ||
+			!$registrar->hasFeature('is_ownable')) {
 			return false;
 		}
 
@@ -682,13 +685,9 @@
 		static $ownable = null;
 
 		if ($ownable === null) {
-			$ownable = [];
-			foreach (JVB_TAXONOMY as $taxonomy => $config) {
-				if (Features::forTaxonomy($taxonomy)->has('is_content') &&
-					Features::forTaxonomy($taxonomy)->has('is_ownable')) {
-					$ownable[] = $taxonomy;
-				}
-			}
+			$ownable = array_map(function ($instance) {
+				return $instance->slug;
+			}, Registrar::getFeatured('is_ownable', 'term'));
 		}
 
 		return $ownable;
@@ -704,14 +703,24 @@
 		static $invitable = null;
 
 		if ($invitable === null) {
-			$invitable = [];
-			foreach (JVB_TAXONOMY as $taxonomy => $config) {
-				if (Features::forTaxonomy($taxonomy)->has('invitable')) {
-					$invitable[] = $taxonomy;
-				}
-			}
+			$invitable = array_map(function ($instance) {
+				return $instance->slug;
+			}, Registrar::getFeatured('invitable', 'term'));
 		}
 
 		return $invitable;
 	}
+
+	public function getPermission(string $action, string $content, ?int $ID = null):?string
+	{
+		$plural = $this->getContentPlural($content);
+		switch ($action) {
+			case 'edit':
+				if ($ID) {
+					return "edit_{$content}";
+				}
+				return "edit_{$plural}";
+		}
+		return null;
+	}
 }
diff --git a/inc/managers/SEO/BreadcrumbManager.php b/inc/managers/SEO/BreadcrumbManager.php
index 9fb5d6d..8d3288e 100644
--- a/inc/managers/SEO/BreadcrumbManager.php
+++ b/inc/managers/SEO/BreadcrumbManager.php
@@ -2,6 +2,7 @@
 namespace JVBase\managers\SEO;
 
 use JVBase\managers\Cache;
+use JVBase\registrar\Registrar;
 use JVBase\utility\Features;
 use WP_Post;
 use WP_Term;
@@ -24,6 +25,7 @@
 	private function __construct()
 	{
 		$this->cache = Cache::for('breadcrumbs', MONTH_IN_SECONDS)->connect('post')->connect('taxonomy')->connect('user');
+		$this->cache->flush();
 		if (JVB_TESTING) {
 			$this->cache->flush();
 		}
@@ -83,7 +85,7 @@
 
 		// Always start with home
 		$crumbs[] = [
-			'name' => 'Home',
+			'name' => get_bloginfo('name'),
 			'icon' => jvbIcon('house'),
 			'url'  => get_home_url(),
 		];
@@ -108,23 +110,27 @@
 	private function addTaxonomyCrumbs(array $crumbs, WP_Term $term): array
 	{
 		$tax = jvbNoBase($term->taxonomy);
-		$config = Features::getConfig($tax, 'term');
-
+		$registrar = Registrar::getInstance($tax);
 		// Add parent content archive if taxonomy is for single content type
-		if (count($config['for_content']) === 1) {
-			$contentConfig = JVB_CONTENT[$config['for_content'][0]];
-			$crumbs[] = [
-				'name' => $contentConfig['breadcrumb'] ?? $contentConfig['plural'],
-				'url'  => get_post_type_archive_link(jvbCheckBase($config['for_content'][0])),
-			];
-			$crumbs[] = [
-				'name' => 'By ' . $config['singular'],
-				'url'  => false,
-			];
+		if ($registrar) {
+			if (count($registrar->registrar->for) === 1){
+				$content = is_array($registrar->registrar->for) ? $registrar->registrar->for[0] : $registrar->registrar->for;
+				$contentRegistrar = Registrar::getInstance($content);
+
+				$crumbs[] = [
+					'name' => $contentRegistrar->getConfig('breadcrumbs')['title']??$contentRegistrar->getPlural(),
+					'url'  => get_post_type_archive_link(jvbCheckBase($content)),
+				];
+				$crumbs[] = [
+					'name' => 'By ' . $registrar->getSingular(),
+					'url'  => false,
+				];
+			}
 		}
 
+
 		// Add directory if exists
-		if (Features::forTaxonomy($tax)->has('directory')) {
+		if ($registrar && $registrar->hasFeature('directory')) {
 			$directory = JVB()->directories()?->directories($tax);
 			$crumbs[] = [
 				'name' => $directory['title'],
@@ -143,8 +149,12 @@
 	{
 		// Add directory if exists
 		$content = jvbNoBase($post->post_type);
-		if(Features::forContent($content)->has('show_directory')) {
-			$directory = JVB()->directories()->getDirectoryList()[$content]??[];
+		$registrar = Registrar::getInstance($content);
+		if ($registrar){
+			$crumbConfig = $registrar->getConfig('breadcrumbs');
+		}
+		if($registrar && $registrar->hasFeature('show_directory')) {
+			$directory = JVB()->directories()?->directories($content)??[];
 			if (!empty($directory)) {
 				$crumbs[] = [
 					'name'	=> $directory['title'],
@@ -154,7 +164,7 @@
 		}
 
 		// Handle directory posts specially
-		if (JVB()->directories()->isDirectory()) {
+		if (JVB()->directories() && JVB()->directories()->isDirectory()) {
 			$pos = jvbGetDirectoryInfo();
 			if (!empty($pos)) {
 				// Special case for map
@@ -171,9 +181,9 @@
 				];
 			}
 		} else {
-			$name = jvbNoBase($post->post_type);
-			if (array_key_exists($name, JVB_CONTENT) && array_key_exists('addCrumb', JVB_CONTENT[$name])) {
-				$crumbs = $this->addTaxToCrumbs($crumbs, JVB_CONTENT[$name]['addCrumb']);
+
+			if ($registrar && !empty($crumbConfig['addCrumb'])) {
+				$crumbs = $this->addTaxToCrumbs($crumbs, $crumbConfig['addCrumb']);
 			}
 			// Add post hierarchy
 			$crumbs = array_merge($crumbs, $this->buildPostHierarchy($post));
@@ -190,14 +200,16 @@
 		$type = is_singular() ? $obj->post_type : $obj->name;
 		$name = jvbNoBase($type);
 
+		$registrar = Registrar::getInstance($name);
 		if (Features::forSite()->has('is_directory') && $name === 'directory') {
 			$crumbs[] = [
 				'name'	=> JVB()->directories()->referAs(true),
 				'url'	=> get_post_type_archive_link($type)
 			];
-		} elseif ((is_post_type_archive() || !Features::forContent($name)->has('show_directory')) && array_key_exists($name, JVB_CONTENT)) {
+		} elseif (is_post_type_archive() && $registrar && $registrar->hasFeature('show_directory')) {
+
 			$crumbs[] = [
-				'name' => JVB_CONTENT[$name]['breadcrumb'] ?? JVB_CONTENT[$name]['plural'],
+				'name' => $registrar->getConfig('breadcrumb')['title'] ?? $registrar->getPlural(),
 				'url'  => get_post_type_archive_link($type)
 			];
 		}
@@ -317,17 +329,19 @@
 		$items = [];
 		$position = 1;
 
+		global $wp;
+		$current = home_url( add_query_arg( $_GET, $wp->request ) );
 		foreach ($crumbs as $crumb) {
 			// Schema requires a URL
 			if ($crumb['url'] === false) {
-				$crumb['url'] = get_permalink();
+				$crumb['url'] = $current;
 			}
 
 			$items[] = [
-				'@type'    => 'ListItem',
-				'position' => $position,
-				'name'     => $crumb['name'],
-				'item'     => $crumb['url'],
+				'@type'    	=> 'ListItem',
+				'@id'		=> $crumb['url'],
+				'position' 	=> $position,
+				'name'     	=> $crumb['name'],
 			];
 
 			$position++;
@@ -335,7 +349,7 @@
 
 		return [
 			'@type'           => 'BreadcrumbList',
-			'@id'             => get_permalink() . '/#breadcrumbs',
+			'@id'             => $current . '/#breadcrumbs',
 			'itemListElement' => $items
 		];
 	}
diff --git a/inc/managers/SEO/ConfigManager.php b/inc/managers/SEO/ConfigManager.php
index 7fe9718..b16a675 100644
--- a/inc/managers/SEO/ConfigManager.php
+++ b/inc/managers/SEO/ConfigManager.php
@@ -1,6 +1,8 @@
 <?php
 namespace JVBase\managers\SEO;
 
+use JVBase\registrar\Registrar;
+
 if (!defined('ABSPATH')) {
 	exit;
 }
@@ -144,10 +146,8 @@
 
 			default:
 				// Try to find in content, taxonomy, or user configs
-				$config = $this->findInConstants($type);
-				if (array_key_exists('seo', $config) && is_array($config['seo'])) {
-					$config = $config['seo'];
-				}
+				$registrar = Registrar::getInstance($type);
+				$config = $registrar->getConfig('seo');
 
 				// If asking for archive config and none exists, provide default
 				if ($configType === 'archive' && !isset($config['archive'])) {
@@ -161,22 +161,6 @@
 				return $config[$configType] ?? [];
 		}
 	}
-	/**
-	 * Find configuration in JVB constants
-	 */
-	private function findInConstants(string $type): array
-	{
-		if (defined('JVB_CONTENT') && isset(JVB_CONTENT[$type])) {
-			return JVB_CONTENT[$type];
-		}
-		if (defined('JVB_TAXONOMY') && isset(JVB_TAXONOMY[$type])) {
-			return JVB_TAXONOMY[$type];
-		}
-		if (defined('JVB_USER') && isset(JVB_USER[$type])) {
-			return JVB_USER[$type];
-		}
-		return [];
-	}
 
 	public function resetConfig(): bool
 	{
diff --git a/inc/managers/SEO/SEOAdminPage.php b/inc/managers/SEO/SEOAdminPage.php
index 63b8671..845b3c8 100644
--- a/inc/managers/SEO/SEOAdminPage.php
+++ b/inc/managers/SEO/SEOAdminPage.php
@@ -3,6 +3,7 @@
 
 use JVBase\managers\AdminPages;
 use JVBase\meta\Form;
+use JVBase\registrar\Registrar;
 use JVBase\ui\Tabs;
 
 if (!defined('ABSPATH')) {
@@ -19,11 +20,11 @@
 class SEOAdminPage
 {
     private ConfigManager $config;
-    private SchemaBuilder $registry;
+//    private SchemaBuilder $registry;
 
     public function __construct()
     {
-        $this->registry = SchemaBuilder::getInstance();
+//        $this->registry = SchemaBuilder::getInstance();
 
 
         // Add to JVB dashboard
@@ -192,23 +193,16 @@
 	{
 		$types = ['meta', 'schema'];
 
-		switch ($type) {
-			case 'content':
-				$config = JVB_CONTENT;
-				$types[] = 'archive';
-				break;
-			case 'taxonomy':
-			case 'taxonomies':
-				$config = JVB_TAXONOMY;
-				break;
-			case 'user':
-				$config = JVB_USER;
-				break;
-			default:
-				error_log('[SEOAdminPage]:renderConfig --- no config found for '.$type);
-				return '';
+		if ($type == 'content') {
+			$types[] = 'archive';
 		}
 
+		$registrar = Registrar::getInstance($type);
+		if (!$registrar){
+			return '';
+		}
+		$config = $registrar->getConfig('seo');
+
 		$mainTabs = new Tabs();
 
 		foreach ($config as $c => $opt) {
diff --git a/inc/managers/SEO/SchemaBuilder.php b/inc/managers/SEO/SchemaBuilder.php
index 9ed0174..d4a2ee6 100644
--- a/inc/managers/SEO/SchemaBuilder.php
+++ b/inc/managers/SEO/SchemaBuilder.php
@@ -1,6 +1,8 @@
 <?php
 namespace JVBase\managers\SEO;
 
+use JVBase\registrar\Registrar;
+
 if (!defined('ABSPATH')) {
 	exit;
 }
@@ -243,9 +245,11 @@
 	{
 		$options = ['' => '-- Select Post Type --'];
 
-		if (defined('JVB_CONTENT')) {
-			foreach (JVB_CONTENT as $key => $config) {
-				$options[jvbCheckBase($key)] = $config['plural'] ?? $config['singular'] ?? ucwords($key);
+		$content = Registrar::getRegistered('post');
+		if (!empty($content)){
+			foreach ($content as $c) {
+				$registrar = Registrar::getInstance($c);
+				$options[jvbCheckBase($c)] = $registrar->getPlural();
 			}
 		}
 
@@ -259,9 +263,12 @@
 	{
 		$options = ['' => '-- Select Taxonomy --'];
 
-		if (defined('JVB_TAXONOMY')) {
-			foreach (JVB_TAXONOMY as $key => $config) {
-				$options[jvbCheckBase($key)] = $config['plural'] ?? $config['singular'] ?? ucwords($key);
+
+		$tax = Registrar::getRegistered('term');
+		if (!empty($tax)){
+			foreach ($tax as $c) {
+				$registrar = Registrar::getInstance($c);
+				$options[jvbCheckBase($c)] = $registrar->getPlural();
 			}
 		}
 
diff --git a/inc/managers/SEO/SchemaOutputManager.php b/inc/managers/SEO/SchemaOutputManager.php
index 9c4a545..e7a44f4 100644
--- a/inc/managers/SEO/SchemaOutputManager.php
+++ b/inc/managers/SEO/SchemaOutputManager.php
@@ -5,6 +5,7 @@
 use JVBase\managers\SEO\schemas\SchemaResolverRegistry;
 use JVBase\meta\Meta;
 use JVBase\managers\SEO\schemas\SchemaDefinition;
+use JVBase\registrar\Registrar;
 use WP_Term;
 use WP_User;
 
@@ -61,21 +62,16 @@
 	 */
 	public function excludeHiddenSingles(array $ids): array
 	{
-		$hiddenTypes = [];
-		$timelineTypes = [];
+		$hiddenTypes = array_map(function($type) {
+				return jvbCheckBase($type);
+			},
+			Registrar::getFeatured('hide_single', 'post')
+		);
 
-		// Find post types with hide_single or is_timeline flags
-		foreach (JVB_CONTENT as $slug => $config) {
-			$postType = BASE . $slug;
+		$timelineTypes = array_map(function($type) {
+			return jvbCheckBase($type);
+		}, Registrar::getFeatured('is_timeline', 'post'));
 
-			if (!empty($config['hide_single'])) {
-				$hiddenTypes[] = $postType;
-			}
-
-			if (!empty($config['is_timeline'])) {
-				$timelineTypes[] = $postType;
-			}
-		}
 
 		$hiddenIds = [];
 
@@ -570,7 +566,7 @@
 			$post = get_post();
 			if ($post) {
 				$postType = jvbNoBase($post->post_type);
-				if (defined('JVB_CONTENT') && isset(JVB_CONTENT[$postType])) {
+				if (Registrar::getInstance($postType)) {
 					$this->config = ConfigManager::for($postType);
 					return [
 						'objectType' => 'post',
@@ -583,7 +579,7 @@
 			$term = get_queried_object();
 			if ($term instanceof WP_Term) {
 				$taxonomy = jvbNoBase($term->taxonomy);
-				if (defined('JVB_TAXONOMY') && isset(JVB_TAXONOMY[$taxonomy])) {
+				if (Registrar::getInstance($taxonomy)) {
 					$this->config = ConfigManager::for($taxonomy);
 					return [
 						'objectType' => 'term',
@@ -596,7 +592,7 @@
 			$user = get_queried_object();
 			if ($user instanceof WP_User) {
 				$role = jvbUserRole($user->ID);
-				if (defined('JVB_USER') && isset(JVB_USER[$role])) {
+				if (Registrar::getInstance($role)) {
 					$this->config = ConfigManager::for($role);
 					return [
 						'objectType' => 'user',
@@ -612,7 +608,7 @@
 			}
 			$postType = jvbNoBase($postType);
 
-			if (defined('JVB_CONTENT') && isset(JVB_CONTENT[$postType])) {
+			if (Registrar::getInstance($postType)) {
 				$this->config = ConfigManager::for($postType);
 				return [
 					'objectType' => 'archive',
diff --git a/inc/managers/SEO/SchemaReferenceBuilder.php b/inc/managers/SEO/SchemaReferenceBuilder.php
index 6627899..ed362fc 100644
--- a/inc/managers/SEO/SchemaReferenceBuilder.php
+++ b/inc/managers/SEO/SchemaReferenceBuilder.php
@@ -2,6 +2,7 @@
 namespace JVBase\managers\SEO;
 
 use JVBase\meta\Meta;
+use JVBase\registrar\Registrar;
 
 if (!defined('ABSPATH')) {
 	exit;
@@ -44,8 +45,11 @@
 		}
 
 		// Get config for templates and schema type
-		$config = self::getConfigFor($objectType, $objectId);
-		$schemaConfig = $config['seo']['schema'] ?? [];
+		$schemaConfig = [];
+		$registrar = Registrar::getInstance($objectType);
+		if ($registrar) {
+			$schemaConfig  = $registrar->getConfig('seo')['schema']??[];
+		}
 
 		// Determine schema type
 		if ($schemaType === null) {
@@ -63,9 +67,7 @@
 		}
 
 		// Create resolver for template resolution
-		$resolver = $objectType === 'post' ? new TemplateResolver($objectId,'post') :
-			($objectType === 'term' ? new TemplateResolver($objectId,'term') :
-				($objectType === 'user' ? new TemplateResolver($objectId, 'user') : null));
+		$resolver = new TemplateResolver($objectId, $objectType)??null;
 
 		// Build reference based on schema type
 		switch ($schemaType) {
@@ -134,38 +136,6 @@
 		};
 	}
 
-	/**
-	 * Get config for an object
-	 */
-	private static function getConfigFor(string $objectType, int $objectId): ?array
-	{
-		switch ($objectType) {
-			case 'post':
-				$postType = get_post_type($objectId);
-				$typeKey = str_replace(BASE, '', $postType);
-				return defined('JVB_CONTENT') && isset(JVB_CONTENT[$typeKey])
-					? JVB_CONTENT[$typeKey]
-					: null;
-
-			case 'term':
-				$term = get_term($objectId);
-				if (!$term || is_wp_error($term)) {
-					return null;
-				}
-				$typeKey = str_replace(BASE, '', $term->taxonomy);
-				return defined('JVB_TAXONOMY') && isset(JVB_TAXONOMY[$typeKey])
-					? JVB_TAXONOMY[$typeKey]
-					: null;
-
-			case 'user':
-				$role = jvbUserRole($objectId);
-				return defined('JVB_USER') && isset(JVB_USER[$role])
-					? JVB_USER[$role]
-					: null;
-		}
-
-		return null;
-	}
 
 	/**
 	 * Build just an @id reference (most minimal)
@@ -402,39 +372,31 @@
 	 */
 	private static function inferSchemaType(string $objectType, int $objectId): string
 	{
+		$default = 'Thing';
+		$registrar = false;
 		if ($objectType === 'post') {
 			$postType = get_post_type($objectId);
-			$typeKey = str_replace(BASE, '', $postType);
-
-			if (defined('JVB_CONTENT') && isset(JVB_CONTENT[$typeKey])) {
-				$config = JVB_CONTENT[$typeKey];
-				return $config['seo']['schema']['type'] ?? 'CreativeWork';
-			}
-
-			return 'CreativeWork';
-		}
-
-		if ($objectType === 'term') {
+			$registrar = Registrar::getInstance($postType));
+			$default = 'CreativeWork';
+		} else if ($objectType === 'term') {
 			$term = get_term($objectId);
 			if (!$term || is_wp_error($term)) {
 				return 'DefinedTerm';
 			}
 
-			$taxonomyKey = str_replace(BASE, '', $term->taxonomy);
-
-			if (defined('JVB_TAXONOMY') && isset(JVB_TAXONOMY[$taxonomyKey])) {
-				$config = JVB_TAXONOMY[$taxonomyKey];
-				return $config['seo']['schema']['type'] ?? 'DefinedTerm';
-			}
-
-			return 'DefinedTerm';
-		}
-
-		if ($objectType === 'user') {
+			$registrar = Registrar::getInstance($term->taxonomy));
+			$default = 'DefinedTerm';
+		} elseif ($objectType === 'user') {
 			return 'Person';
 		}
 
-		return 'Thing';
+		$type = false;
+
+		if ($registrar) {
+			$type = $registrar->getConfig('seo')['schema']['type'];
+		}
+
+		return ($type && !empty($type)) ? $type : $default;
 	}
 
 	/**
diff --git a/inc/managers/SEO/TemplateResolver.php b/inc/managers/SEO/TemplateResolver.php
index cda61ed..bae31b9 100644
--- a/inc/managers/SEO/TemplateResolver.php
+++ b/inc/managers/SEO/TemplateResolver.php
@@ -2,6 +2,7 @@
 namespace JVBase\managers\SEO;
 
 use JVBase\meta\Meta;
+use JVBase\registrar\Registrar;
 use WP_Post;
 use WP_Term;
 use WP_User;
@@ -187,14 +188,14 @@
 	 */
 	private function isRelatedPostsField(string $fieldName): bool
 	{
-		if (!defined('JVB_CONTENT')) {
+		$posts = Registrar::getRegistered('post');
+		if (empty($posts)) {
 			return false;
 		}
 
-		// Check if field name matches any plural content type
-		foreach (JVB_CONTENT as $type => $config) {
-			$plural = strtolower($config['plural'] ?? '');
-			if ($plural && $fieldName === $plural) {
+		foreach ($posts as $post) {
+			$registrar = Registrar::getInstance($post);
+			if ($registrar && strtolower($registrar->getPlural()) === $fieldName){
 				return true;
 			}
 		}
@@ -209,14 +210,15 @@
 	 */
 	private function isRelatedTermsField(string $fieldName): bool
 	{
-		if (!defined('JVB_TAXONOMY')) {
+
+		$posts = Registrar::getRegistered('term');
+		if (empty($posts)) {
 			return false;
 		}
 
-		// Check if field name matches any plural taxonomy
-		foreach (JVB_TAXONOMY as $taxonomy => $config) {
-			$plural = strtolower($config['plural'] ?? '');
-			if ($plural && $fieldName === $plural) {
+		foreach ($posts as $post) {
+			$registrar = Registrar::getInstance($post);
+			if ($registrar && strtolower($registrar->getPlural()) === $fieldName){
 				return true;
 			}
 		}
@@ -277,17 +279,20 @@
 	 */
 	private function getPostTypeFromPluralName(string $pluralName): ?string
 	{
-		if (!defined('JVB_CONTENT')) {
+
+		$posts = Registrar::getRegistered('post');
+		if (empty($posts)) {
 			return null;
 		}
 
-		foreach (JVB_CONTENT as $type => $config) {
-			$plural = strtolower($config['plural'] ?? '');
-			if ($plural === $pluralName) {
-				return $type;
+		foreach ($posts as $post) {
+			$registrar = Registrar::getInstance($post);
+			if ($registrar && strtolower($registrar->getPlural()) === $pluralName){
+				return $post;
 			}
 		}
 
+
 		return null;
 	}
 
@@ -296,17 +301,18 @@
 	 */
 	private function getTaxonomyFromPluralName(string $pluralName): ?string
 	{
-		if (!defined('JVB_TAXONOMY')) {
+
+		$posts = Registrar::getRegistered('term');
+		if (empty($posts)) {
 			return null;
 		}
 
-		foreach (JVB_TAXONOMY as $taxonomy => $config) {
-			$plural = strtolower($config['plural'] ?? '');
-			if ($plural === $pluralName) {
-				return $taxonomy;
+		foreach ($posts as $post) {
+			$registrar = Registrar::getInstance($post);
+			if ($registrar && strtolower($registrar->getPlural()) === $pluralName){
+				return $post;
 			}
 		}
-
 		return null;
 	}
 
@@ -650,14 +656,9 @@
 			return;
 		}
 
-		$typeKey = str_replace(BASE, '', $this->contentType);
-
-		if ($this->objectType === 'post' && defined('JVB_CONTENT')) {
-			$this->fieldDefinitions = JVB_CONTENT[$typeKey]['fields'] ?? [];
-		} elseif ($this->objectType === 'term' && defined('JVB_TAXONOMY')) {
-			$this->fieldDefinitions = JVB_TAXONOMY[$typeKey]['fields'] ?? [];
-		} elseif ($this->objectType === 'user' && defined('JVB_USER')) {
-			$this->fieldDefinitions = JVB_USER[$typeKey]['fields'] ?? [];
+		$registrar = Registrar::getInstance($this->contentType));
+		if ($registrar) {
+			$this->fieldDefinitions = $registrar->getFields();
 		}
 	}
 }
diff --git a/inc/managers/SEO/_setup.php b/inc/managers/SEO/_setup.php
index 29f48f1..07a0e80 100644
--- a/inc/managers/SEO/_setup.php
+++ b/inc/managers/SEO/_setup.php
@@ -1,15 +1,19 @@
 <?php
-require(JVB_DIR . '/inc/managers/SEO/schemas/_setup.php');
+//New System
+require(JVB_DIR . '/inc/managers/SEO/render/_setup.php');
 
-require(JVB_DIR . '/inc/managers/SEO/FieldBuilder.php');
-require(JVB_DIR . '/inc/managers/SEO/FieldOverrideBuilder.php');
-require(JVB_DIR . '/inc/managers/SEO/TypeBuilder.php');
-require(JVB_DIR . '/inc/managers/SEO/SchemaBuilder.php');
-require(JVB_DIR.'/base/seo.php');
-require(JVB_DIR . '/inc/managers/SEO/ConfigManager.php');
+
+//require(JVB_DIR . '/inc/managers/SEO/schemas/_setup.php');
+//
+//require(JVB_DIR . '/inc/managers/SEO/FieldBuilder.php');
+//require(JVB_DIR . '/inc/managers/SEO/FieldOverrideBuilder.php');
+//require(JVB_DIR . '/inc/managers/SEO/TypeBuilder.php');
+//require(JVB_DIR . '/inc/managers/SEO/SchemaBuilder.php');
+//require(JVB_DIR.'/base/seo.php');
+//require(JVB_DIR . '/inc/managers/SEO/ConfigManager.php');
 require(JVB_DIR . '/inc/managers/SEO/BreadcrumbManager.php');
-require(JVB_DIR . '/inc/managers/SEO/SchemaFieldHelpers.php');
-require(JVB_DIR . '/inc/managers/SEO/SchemaReferenceBuilder.php');
-require(JVB_DIR . '/inc/managers/SEO/TemplateResolver.php');
-require(JVB_DIR . '/inc/managers/SEO/SchemaOutputManager.php');
+//require(JVB_DIR . '/inc/managers/SEO/SchemaFieldHelpers.php');
+//require(JVB_DIR . '/inc/managers/SEO/SchemaReferenceBuilder.php');
+//require(JVB_DIR . '/inc/managers/SEO/TemplateResolver.php');
+//require(JVB_DIR . '/inc/managers/SEO/SchemaOutputManager.php');
 require(JVB_DIR . '/inc/managers/SEO/SEOAdminPage.php');
diff --git a/inc/managers/SEO/render/DataType/Date.php b/inc/managers/SEO/render/DataType/Date.php
new file mode 100644
index 0000000..7521f08
--- /dev/null
+++ b/inc/managers/SEO/render/DataType/Date.php
@@ -0,0 +1,40 @@
+<?php
+namespace JVBase\managers\SEO\render\DataType;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+final class Date {
+	/**
+	 * @var string Time in ISO 8601 format, hh:mm:ss[Z|(+|-)hh:mm]
+	 */
+	protected string $date;
+
+	/**
+	 * @throws \DateMalformedStringException
+	 */
+	public function __construct(string $date)
+	{
+		$this->setDate($date);
+	}
+
+	public function getDate():?string {
+		if (!isset($this->date)) {
+			return null;
+		}
+		return $this->date;
+	}
+
+	/**
+	 * @throws \DateMalformedStringException
+	 */
+	public function setDate(string $date):void
+	{
+		$time = new \DateTime(strtotime($date));
+		$time = $time->format('c');
+		if ($time){
+			$this->date = $time;
+		}
+	}
+}
diff --git a/inc/managers/SEO/render/DataType/DateTime.php b/inc/managers/SEO/render/DataType/DateTime.php
new file mode 100644
index 0000000..a08a775
--- /dev/null
+++ b/inc/managers/SEO/render/DataType/DateTime.php
@@ -0,0 +1,42 @@
+<?php
+namespace JVBase\managers\SEO\render\DataType;
+
+use DateMalformedStringException;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+final class DateTime {
+	/**
+	 * @var string Time in ISO 8601 format, hh:mm:ss[Z|(+|-)hh:mm]
+	 */
+	protected string $dateTime;
+
+	/**
+	 * @throws DateMalformedStringException
+	 */
+	public function __construct(string $date){
+		error_log('Processing date: '.$date);
+		$this->setDateTime($date);
+	}
+
+	public function getDateTime():?string {
+		if (!isset($this->dateTime)) {
+			return null;
+		}
+		return $this->dateTime;
+	}
+
+	/**
+	 * @throws DateMalformedStringException
+	 */
+	public function setDateTime(string $date):void {
+
+		$time = new \DateTime($date);
+		$time = $time->format('c');
+		if ($time){
+			$this->dateTime = $time;
+		}
+	}
+}
diff --git a/inc/managers/SEO/render/DataType/Time.php b/inc/managers/SEO/render/DataType/Time.php
new file mode 100644
index 0000000..0069ea4
--- /dev/null
+++ b/inc/managers/SEO/render/DataType/Time.php
@@ -0,0 +1,33 @@
+<?php
+namespace JVBase\managers\SEO\render\DataType;
+use DateMalformedStringException;
+use DateTime;
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+final class Time {
+	/**
+	 * @var string
+	 */
+	protected string $time;
+
+	public function __construct(string $time)
+	{
+		$this->setTime($time);
+	}
+	public function getTime():string {
+		return $this->time;
+	}
+
+	/**
+	 * @throws DateMalformedStringException
+	 */
+	public function setTime(string $time):void {
+		$time = new DateTime(strtotime($time));
+		$time = $time->format('H:i:s');
+		if ($time){
+			$this->time = $time;
+		}
+	}
+}
diff --git a/inc/managers/SEO/render/DataType/_setup.php b/inc/managers/SEO/render/DataType/_setup.php
new file mode 100644
index 0000000..0889db9
--- /dev/null
+++ b/inc/managers/SEO/render/DataType/_setup.php
@@ -0,0 +1,4 @@
+<?php
+require(JVB_DIR . '/inc/managers/SEO/render/DataType/Date.php');
+require(JVB_DIR . '/inc/managers/SEO/render/DataType/DateTime.php');
+require(JVB_DIR . '/inc/managers/SEO/render/DataType/Time.php');
diff --git a/inc/managers/SEO/render/SchemaOutput.php b/inc/managers/SEO/render/SchemaOutput.php
new file mode 100644
index 0000000..d187f7d
--- /dev/null
+++ b/inc/managers/SEO/render/SchemaOutput.php
@@ -0,0 +1,238 @@
+<?php
+namespace JVBase\managers\SEO\render;
+
+use JVBase\managers\Cache;
+use JVBase\managers\SEO\BreadcrumbManager;
+use JVBase\managers\SEO\render\Thing\CreativeWork\WebSite;
+use JVBase\managers\SEO\render\Thing\Intangible\OfferCatalog;
+use JVBase\managers\SEO\render\Thing\Intangible\Service;
+use JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\LocalBusiness;
+use JVBase\registrar\config\seo\Resolver;
+use JVBase\registrar\Registrar;
+
+class SchemaOutput {
+	protected array $types;
+	public function __construct() {
+		add_action('wp_head', [$this, 'outputSchema'], 0);
+		$this->init();
+	}
+
+	protected function init(): void {
+		$this->types = array_map(function ($type) { return jvbCheckBase($type); }, Registrar::getRegistered());
+	}
+	public function buildSchema():array
+	{
+		$schema = [];
+		if (!is_front_page()) {
+			$schema[] = $this->buildBasicWebsiteSchema();
+		}
+		if (is_singular($this->types)) {
+			$type = get_post_type();
+			$registrar = Registrar::getInstance($type);
+			if ($registrar && !empty($registrar->getConfig('seo')['schema']??[])) {
+				$seo = $registrar->getSEO();
+				$schema[] = $seo->schema()->outputSingularSchema();
+			}
+		}elseif (is_post_type_archive($this->types)) {
+
+			$type = get_queried_object();
+			$type = $type->name;
+			$registrar = Registrar::getInstance($type);
+
+			if ($registrar && !empty($registrar->getConfig('seo')['schema']??[])) {
+				$seo = $registrar->getSEO();
+				$schema[] =$seo->schema()->outputArchiveSchema();
+			}
+		} elseif (is_tax($this->types)) {
+			$type = get_queried_object();
+			$type = jvbNoBase($type->taxonomy);
+			$registrar = Registrar::getInstance($type);
+			if ($registrar && !empty($registrar->getConfig('seo')['schema']??[])) {
+				$seo = $registrar->getSEO();
+				$schema[] = $seo->schema()->outputArchiveSchema();
+			}
+		}
+
+		$breadcrumbs = $this->buildBreadcrumbs();
+		if ($breadcrumbs) {
+			$schema[] = $breadcrumbs;
+		}
+
+		if (!empty($schema)) {
+			$website = get_option(BASE.'WebsiteSchema');
+			if (!empty($website)) {
+				Cache::for('websiteSchema')->flush();
+				$website = Cache::for('websiteSchema')->remember(
+					'schema',
+					function () {
+						return $this->websiteSchema();
+					}
+				);
+				array_unshift($schema, $website);
+			}
+			$schema = [
+				'@context'		=> 'https://schema.org',
+				'@graph'		=> $schema
+			];
+		}
+		return $schema;
+	}
+
+	protected function websiteSchema():array
+	{
+		$stored = get_option(BASE.'WebsiteSchema', apply_filters(BASE.'WebsiteSchema', []));
+
+		$seo = new WebSite();
+		foreach ($stored as $property => $value) {
+			$method = 'set'.ucfirst($property);
+			$seo->$method($value);
+		}
+		$seo->setUrl(get_home_url());
+		$seo->setName(get_bloginfo('name'));
+
+		$seo->setCreator($this->getCreator());
+		return $seo->outputSchema();
+	}
+
+	public function outputSchema():void
+	{
+		$schema = $this->buildSchema();
+		if (empty($schema)) {
+			return;
+		}
+		echo "\n<!-- SEO Schema by JakeVan -->\n";
+		echo '<script type="application/ld+json">' . "\n";
+		echo wp_json_encode($schema, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
+		echo "\n" . '</script>' . "\n";
+	}
+
+	public function buildBreadcrumbs():array
+	{
+		return BreadcrumbManager::getInstance()->toSchema();
+	}
+
+	public function buildBasicWebsiteSchema():array
+	{	Cache::for('websiteSchema')->flush();
+		return Cache::for('websiteSchema')->remember(
+			'reference',
+			function () {
+				$website = new WebSite();
+				$website->setName(get_bloginfo('name'));
+				$website->setUrl(get_home_url());
+				$website->setId(get_home_url().'/#website');
+				$website->setDescription(get_bloginfo('description'));
+				$website->setInLanguage('en-CA');
+				$publisher = $this->getOptionSchemaReference('organization');
+				if ($publisher){
+					$website->setPublisher($publisher);
+				}
+
+				$website->setCreator($this->getCreator(true));
+
+				return $website->outputSchema();
+			}
+		);
+	}
+
+
+	public function getCreator(bool $reference = false):LocalBusiness
+	{
+		Cache::for('JakeVanCreator')->flush();
+		if ($reference) {
+			return Cache::for('JakeVanCreator')->remember(
+				'reference',
+				function () {
+					$creator = new LocalBusiness();
+					$creator->setName('JakeVan');
+					$creator->setAlternateName('Jake Vanderwerf');
+					$creator->setUrl('https://jakevan.ca/');
+					$creator->setDescription('Let\'s bring your idea to life.');
+					return $creator;
+				}
+			);
+		}
+
+		return Cache::for('JakeVanCreator')->remember(
+			'full',
+			function () {
+				$creator = new LocalBusiness();
+				$creator->setName('JakeVan');
+				$creator->setId('https://jakevan.ca/#localbusiness');
+				$creator->setAlternateName(['Jake Vanderwerf', 'Rooted Romantic', 'Jacob Vanderwerf']);
+				$creator->setUrl('https://jakevan.ca');
+				$creator->setSameAs([
+					'https://bsky.app/profile/jakevan.ca',
+				]);
+				$creator->setAreaServed(['Edmonton, Alberta', 'Alberta', 'Canada']);
+				$offers = new OfferCatalog();
+				$offers->setName('Services');
+				$offers->setUrl('https://jakevan.ca/services');
+
+				$graphicDesign = new Service();
+				$graphicDesign->setName('Graphic Design');
+				$graphicDesign->setUrl('https://jakevan.ca/services/design/');
+				$graphicDesign->setDescription('From print to digital design.');
+				$websiteDesign = new Service();
+				$websiteDesign->setName('Development');
+				$websiteDesign->setUrl('https://jakevan.ca/services/development/');
+				$websiteDesign->setDescription('From basic websites to custom functionality.');
+				$strategy = new Service();
+				$strategy->setName('Strategy');
+				$strategy->setUrl('https://jakevan.ca/services/strategy/');
+				$strategy->setDescription('From developing your business plan to SEO.');
+				$art = new Service();
+				$art->setName('Art');
+				$art->setUrl('https://jakevan.ca/services/art/');
+				$art->setDescription('From unique, custom, handmade pieces to small-scale wholesale.');
+
+				$offers->setItemListElement([
+					$graphicDesign,
+					$websiteDesign,
+					$strategy,
+					$art
+				]);
+
+
+				$creator->setHasOfferCatalog($offers);
+				return $creator;
+			}
+		);
+	}
+
+	public function getOptionSchemaReference(string $option):mixed
+	{
+		if (!in_array(strtolower($option), array_merge(
+			['organization',
+			'website'],
+			Registrar::getRegistered()
+		))) {
+			error_log('Attempted to get schema reference for: '.$option.', but it does not exist');
+			return null;
+		}
+		$action = BASE.ucfirst($option).'Schema';
+		$stored = get_option($action, apply_filters($action, []));
+//		$stored = get_option($action, apply_filters($action, jvbDefaultSchema($option)));
+		if (empty($stored)){
+			error_log('Attempted to get schema reference for: '.$option.', but defaults not set.');
+			return null;
+		}
+		$type = $stored['type'];
+		if (!class_exists($type)){
+			error_log('Attempted to get schema reference, but class does not exist: '.$type);
+			return null;
+		}
+		$class = new $type();
+		unset($stored['type']);
+		$minimal = apply_filters($action.'Reference', ['name', 'url', 'sameAs', 'logo']);
+
+		foreach ($minimal as $property) {
+			$method = 'set'.ucfirst($property);
+			$value = array_key_exists($property, $stored) ? $stored[$property] : null;
+			if (!$value) {continue;}
+
+			$class->$method(Resolver::resolve($property, $value));
+		}
+		return $class;
+	}
+}
+
diff --git a/inc/managers/SEO/render/Thing/Action.php b/inc/managers/SEO/render/Thing/Action.php
new file mode 100644
index 0000000..a82035b
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Action.php
@@ -0,0 +1,37 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing;
+
+use JVBase\managers\SEO\render\DataType\DateTime;
+use JVBase\managers\SEO\render\DataType\Time;
+use JVBase\managers\SEO\render\Thing\CreativeWork\HowTo;
+use JVBase\managers\SEO\render\Thing\Intangible\ContactPoint\PostalAddress;
+use JVBase\managers\SEO\render\Thing\Intangible\EntryPoint;
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\ActionStatusType;
+use JVBase\managers\SEO\render\Thing\Intangible\VirtualLocation;
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Properties\actionProcessTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\actionStatusTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\agentTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\endTimeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\errorTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\instrumentTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\locationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\objectTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\participantTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\providerTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\resultTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\startTimeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\targetTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Action {
+	use actionProcessTrait, actionStatusTrait, agentTrait,
+		endTimeTrait, errorTrait, instrumentTrait, locationTrait,
+		objectTrait, participantTrait, providerTrait, resultTrait,
+		startTimeTrait, targetTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/CategoryCodeSet.php b/inc/managers/SEO/render/Thing/CreativeWork/CategoryCodeSet.php
new file mode 100644
index 0000000..c23f014
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/CategoryCodeSet.php
@@ -0,0 +1,12 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\CreativeWork;
+
+use JVBase\managers\SEO\render\Traits\_Properties\hasCategoryCodeTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class CategoryCodeSet extends DefinedTermSet {
+	use hasCategoryCodeTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/Certification.php b/inc/managers/SEO/render/Thing/CreativeWork/Certification.php
new file mode 100644
index 0000000..4174202
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/Certification.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\CreativeWork;
+
+use JVBase\managers\SEO\render\Traits\_Properties\aboutTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\auditDateTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\certificationIdentificationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\certificationRatingTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\certificationStatusTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\datePublishedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\expiresTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\issuedByTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\logoTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\validFromTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\validInTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Certification extends CreativeWork
+{
+	use aboutTrait, auditDateTrait, certificationIdentificationTrait,
+		certificationRatingTrait, certificationStatusTrait, datePublishedTrait,
+		expiresTrait, issuedByTrait, logoTrait, validFromTrait, validInTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/Clip.php b/inc/managers/SEO/render/Thing/CreativeWork/Clip.php
new file mode 100644
index 0000000..e0bf196
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/Clip.php
@@ -0,0 +1,22 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\CreativeWork;
+
+use JVBase\managers\SEO\render\Traits\_Properties\actorTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\clipNumberTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\directorTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\endOffsetTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\musicByTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\startOffsetTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Clip extends CreativeWork {
+	use actorTrait, clipNumberTrait, directorTrait, endOffsetTrait,
+		musicByTrait, startOffsetTrait;
+
+//	protected Episode $partOfEpisode;
+//	protected CreativeWorkSeason $partOfSeason;
+//	protected CreativeWorkSeries $partOfSeries;
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/CreativeWork.php b/inc/managers/SEO/render/Thing/CreativeWork/CreativeWork.php
new file mode 100644
index 0000000..4c84565
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/CreativeWork.php
@@ -0,0 +1,115 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\CreativeWork;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Properties\aboutTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\abstractTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\accountablePersonTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\acquireLicensePageTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\aggregateRatingTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\alternativeHeadlineTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\associatedMediaTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\audienceTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\audioTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\authorTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\awardTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\characterTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\citationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\contentLocationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\contentRatingTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\contentReferenceTimeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\contributorTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\copyrightHolderTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\copyrightNoticeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\copyrightYearTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\countryOfOriginTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\creativeWorkStatusTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\creatorTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\creditTextTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\dateCreatedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\dateModifiedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\discussionUrlTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\displayLocationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\editorTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\encodingFormatTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\encodingTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\exampleOfWorkTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\expiresTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\funderTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\fundingTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\genreTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\hasPartTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\headlineTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\inLanguageTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\interactionStatisticTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\isAccessibleForFreeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\isBasedOnTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\isFamilyFriendlyTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\isPartOfTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\keywordsTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\licenseTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\locationCreatedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\mainEntityTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\maintainerTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\materialExtentTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\materialTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\mentionsTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\offersTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\patternTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\positionTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\producerTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\providerTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\publisherImprintTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\publisherTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\publishingPrinciplesTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\recordedAtTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\reviewTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\sdDatePublishedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\sdLicenseTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\sdPublisherTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\sizeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\sourceOrganizationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\spatialCoverageTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\spatialTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\sponsorTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\teachesTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\temporalCoverageTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\textTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\thumbnailTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\thumbnailUrlTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\timeRequiredTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\translationOfWorkTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\translatorTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\typicalAgeRangeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\versionTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\videoTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\wordCountTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\workExampleTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\workTranslationTrait;
+
+
+class CreativeWork extends Thing {
+	use aboutTrait, abstractTrait, accountablePersonTrait, acquireLicensePageTrait,
+		aggregateRatingTrait, alternativeHeadlineTrait, associatedMediaTrait, audienceTrait,
+		audioTrait, authorTrait, awardTrait, characterTrait, citationTrait, contentLocationTrait,
+		contentReferenceTimeTrait, contentRatingTrait, contributorTrait, copyrightHolderTrait,
+		copyrightNoticeTrait, copyrightYearTrait, countryOfOriginTrait, creativeWorkStatusTrait,
+		creatorTrait, creditTextTrait, dateCreatedTrait, dateModifiedTrait,
+		discussionUrlTrait, displayLocationTrait, editorTrait, encodingTrait, encodingFormatTrait,
+		exampleOfWorkTrait, expiresTrait, funderTrait, fundingTrait, genreTrait, hasPartTrait,
+		headlineTrait, inLanguageTrait, interactionStatisticTrait, isAccessibleForFreeTrait, isBasedOnTrait,
+		isFamilyFriendlyTrait, isPartOfTrait, keywordsTrait, licenseTrait, locationCreatedTrait,
+		mainEntityTrait, maintainerTrait, materialTrait, materialExtentTrait, mentionsTrait,
+		offersTrait, patternTrait, positionTrait, producerTrait, providerTrait, publisherTrait,
+		publisherImprintTrait, publishingPrinciplesTrait, recordedAtTrait, reviewTrait, sdDatePublishedTrait,
+		sdLicenseTrait, sdPublisherTrait, sizeTrait, sourceOrganizationTrait, spatialTrait,
+		spatialCoverageTrait, sponsorTrait, teachesTrait, temporalCoverageTrait, textTrait,
+		thumbnailTrait, thumbnailUrlTrait, timeRequiredTrait, translationOfWorkTrait, translatorTrait,
+		typicalAgeRangeTrait, versionTrait, videoTrait, wordCountTrait, workExampleTrait, workTranslationTrait;
+//	protected PublicationEvent $publication;
+//	protected PublicationEvent $releasedEvent;
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/DefinedTermSet.php b/inc/managers/SEO/render/Thing/CreativeWork/DefinedTermSet.php
new file mode 100644
index 0000000..08e5da9
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/DefinedTermSet.php
@@ -0,0 +1,15 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\CreativeWork;
+
+use JVBase\managers\SEO\render\Thing\Intangible\DefinedTerm;
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Properties\hasDefinedTermTrait;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class DefinedTermSet extends CreativeWork {
+	use hasDefinedTermTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/EducationalOccupationalCredential.php b/inc/managers/SEO/render/Thing/CreativeWork/EducationalOccupationalCredential.php
new file mode 100644
index 0000000..d28ba00
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/EducationalOccupationalCredential.php
@@ -0,0 +1,38 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\CreativeWork;
+
+use JVBase\managers\SEO\render\Thing\Intangible\DefinedTerm;
+use JVBase\managers\SEO\render\Thing\Intangible\Quantity\Duration;
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Place\AdministrativeArea\AdministrativeArea;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class EducationalOccupationalCredential extends CreativeWork {
+	/**
+	 * @var DefinedTerm|string Knowledge, skill, ability or personal attribute that must be demonstrated by a person or other entity in order to do something such as earn an Educational Occupational Credential or understand a LearningResource.
+	 */
+	protected DefinedTerm|string $competencyRequired;
+	/**
+	 * @var DefinedTerm|string The category or type of credential being described, for example "degree”, “certificate”, “badge”, or more specific term.
+	 */
+	protected DefinedTerm|string $credentialCategory;
+	/**
+	 * @var DefinedTerm|string The level in terms of progression through an educational or training context. Examples of educational levels include 'beginner', 'intermediate' or 'advanced', and formal sets of level indicators.
+	 */
+	protected DefinedTerm|string $educationalLevel;
+	/**
+	 * @var Organization An organization that acknowledges the validity, value or utility of a credential. Note: recognition may include a process of quality assurance or accreditation.
+	 */
+	protected Organization $recognizedBy;
+	/**
+	 * @var Duration The duration of validity of a permit or similar thing.
+	 */
+	protected Duration $validFor;
+	/**
+	 * @var AdministrativeArea The geographic area where the item is valid. Applies for example to a Permit, a Certification, or an EducationalOccupationalCredential.
+	 */
+	protected AdministrativeArea $validIn;
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/HowTo.php b/inc/managers/SEO/render/Thing/CreativeWork/HowTo.php
new file mode 100644
index 0000000..5c21dcd
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/HowTo.php
@@ -0,0 +1,42 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\CreativeWork;
+
+use JVBase\managers\SEO\render\Thing\Intangible\ContactPoint\PostalAddress;
+use JVBase\managers\SEO\render\Thing\Intangible\HowToSupply;
+use JVBase\managers\SEO\render\Thing\Intangible\HowToTool;
+use JVBase\managers\SEO\render\Thing\Intangible\Quantity\Duration;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\MonetaryAmount;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Properties\estimateCostTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\performTimeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\prepTimeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\totalTimeTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class HowTo extends CreativeWork {
+	use estimateCostTrait, performTimeTrait, prepTimeTrait, totalTimeTrait;
+
+	/**
+	 * @var CreativeWork|HowToSection|HowToStep|string A single step item (as HowToStep, text, document, video, etc.) or a HowToSection. Supersedes steps.
+	 */
+	protected CreativeWork|HowToSection|HowToStep|string $step;
+	/**
+	 * @var HowToSupply|string A sub-property of instrument. A supply consumed when performing instructions or a direction.
+	 */
+	protected HowToSupply|string $supply;
+	/**
+	 * @var HowToTool|string A sub property of instrument. An object used (but not consumed) when performing instructions or a direction.
+	 */
+	protected HowToTool|string $tool;
+
+	/**
+	 * @var QuantitativeValue|string The quantity that results by performing instructions. For example, a paper airplane, 10 personalized candles.
+	 */
+	protected QuantitativeValue|string $yield;
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/HowToSection.php b/inc/managers/SEO/render/Thing/CreativeWork/HowToSection.php
new file mode 100644
index 0000000..4692461
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/HowToSection.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\CreativeWork;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Quantity\Duration;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\MonetaryAmount;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class HowToSection extends CreativeWork
+{
+
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/HowToStep.php b/inc/managers/SEO/render/Thing/CreativeWork/HowToStep.php
new file mode 100644
index 0000000..949f1ea
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/HowToStep.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\CreativeWork;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Quantity\Duration;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\MonetaryAmount;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class HowToStep extends CreativeWork
+{
+
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/MediaObject/AudioObject.php b/inc/managers/SEO/render/Thing/CreativeWork/MediaObject/AudioObject.php
new file mode 100644
index 0000000..1515907
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/MediaObject/AudioObject.php
@@ -0,0 +1,14 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\CreativeWork\MediaObject;
+
+use JVBase\managers\SEO\render\Traits\_Properties\captionTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\embeddedTextCaptionTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\transcriptTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class AudioObject extends MediaObject {
+	use captionTrait, embeddedTextCaptionTrait, transcriptTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/MediaObject/ImageObject.php b/inc/managers/SEO/render/Thing/CreativeWork/MediaObject/ImageObject.php
new file mode 100644
index 0000000..9fa1165
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/MediaObject/ImageObject.php
@@ -0,0 +1,14 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\CreativeWork\MediaObject;
+
+use JVBase\managers\SEO\render\Traits\_Properties\captionTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\exifDataTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\representativeOfPageTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class ImageObject extends MediaObject {
+	use captionTrait, representativeOfPageTrait, exifDataTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/MediaObject/MediaObject.php b/inc/managers/SEO/render/Thing/CreativeWork/MediaObject/MediaObject.php
new file mode 100644
index 0000000..11ee774
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/MediaObject/MediaObject.php
@@ -0,0 +1,58 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\CreativeWork\MediaObject;
+
+use JVBase\managers\SEO\render\DataType\Date;
+use JVBase\managers\SEO\render\DataType\DateTime;
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Traits\_Properties\bitrateTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\contentSizeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\contentUrlTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\durationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\embedURLTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\encodingFormatTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\endTimeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\heightTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\ineligibleRegionTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\regionsAllowedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\startTimeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\widthTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class MediaObject extends CreativeWork {
+	use bitrateTrait, contentSizeTrait, contentUrlTrait, durationTrait,
+		embedURLTrait, encodingFormatTrait, endTimeTrait, heightTrait,
+		ineligibleRegionTrait, regionsAllowedTrait, startTimeTrait, widthTrait;
+	/**
+	 * var NewsArticle A NewsArticle associated with the Media Object.
+	 */
+//	protected NewsArticle $associatedArticle;
+
+	/**
+	 * @var CreativeWork The CreativeWork encoded by this media object.
+	 * Inverse property: encoding
+	 */
+	protected CreativeWork $encodesCreativeWork;
+
+	/**
+	 * @var string Player type required—for example, Flash or Silverlight.
+	 */
+	protected string $playerType;
+	/**
+	 * @var Organization The production company or studio responsible for the item, e.g. series, video game, episode etc.
+	 */
+	protected Organization $productionCompany;
+
+	/**
+	 * @var bool Indicates if use of the media require a subscription (either paid or free). Allowed values are true or false (note that an earlier version had 'yes', 'no').
+	 */
+	protected bool $requiresSubscription;
+
+	/**
+	 * @var Date|DateTime Date (including time if available) when this media object was uploaded to this site.
+	 */
+	protected Date|DateTime $uploadDate;
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/MediaObject/VideoObject.php b/inc/managers/SEO/render/Thing/CreativeWork/MediaObject/VideoObject.php
new file mode 100644
index 0000000..7578ac7
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/MediaObject/VideoObject.php
@@ -0,0 +1,20 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\CreativeWork\MediaObject;
+
+use JVBase\managers\SEO\render\Traits\_Properties\actorTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\captionTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\directorTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\embeddedTextCaptionTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\musicByTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\transcriptTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\videoFrameSizeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\videoQualityTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class VideoObject extends MediaObject {
+	use actorTrait, captionTrait, directorTrait, embeddedTextCaptionTrait,
+		musicByTrait, transcriptTrait, videoFrameSizeTrait, videoQualityTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/MediaObject/_setup.php b/inc/managers/SEO/render/Thing/CreativeWork/MediaObject/_setup.php
new file mode 100644
index 0000000..ec6beab
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/MediaObject/_setup.php
@@ -0,0 +1,5 @@
+<?php
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/MediaObject/MediaObject.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/MediaObject/AudioObject.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/MediaObject/ImageObject.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/MediaObject/VideoObject.php');
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/Menu.php b/inc/managers/SEO/render/Thing/CreativeWork/Menu.php
new file mode 100644
index 0000000..6799e1c
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/Menu.php
@@ -0,0 +1,13 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\CreativeWork;
+
+use JVBase\managers\SEO\render\Traits\_Properties\hasMenuItemTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\hasMenuSectionTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Menu extends CreativeWork {
+	use hasMenuItemTrait, hasMenuSectionTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/MenuSection.php b/inc/managers/SEO/render/Thing/CreativeWork/MenuSection.php
new file mode 100644
index 0000000..c946238
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/MenuSection.php
@@ -0,0 +1,13 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\CreativeWork;
+
+use JVBase\managers\SEO\render\Traits\_Properties\hasMenuItemTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\hasMenuSectionTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class MenuSection extends CreativeWork {
+	use hasMenuItemTrait, hasMenuSectionTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/MusicRecording.php b/inc/managers/SEO/render/Thing/CreativeWork/MusicRecording.php
new file mode 100644
index 0000000..7430829
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/MusicRecording.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\CreativeWork;
+
+use JVBase\managers\SEO\render\Traits\_Properties\byArtistTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\durationTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class MusicRecording extends CreativeWork {
+	use byArtistTrait, durationTrait;
+
+//	protected MusicAlbum $inAlbum;
+//	protected MusicPlaylist $inPlaylist;
+	/**
+	 * @var string The International Standard Recording Code for the recording.
+	 */
+	protected string $isrcCode;
+//	protected MusicComposition $recordingOf;
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/Photograph.php b/inc/managers/SEO/render/Thing/CreativeWork/Photograph.php
new file mode 100644
index 0000000..eba8e76
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/Photograph.php
@@ -0,0 +1,10 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\CreativeWork;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Photograph extends CreativeWork {
+
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/Review.php b/inc/managers/SEO/render/Thing/CreativeWork/Review.php
new file mode 100644
index 0000000..04949ba
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/Review.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\CreativeWork;
+
+use JVBase\managers\SEO\render\Traits\_Properties\itemReviewedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\negativeNotesTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\positiveNotesTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\reviewBodyTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\reviewRatingTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Review extends CreativeWork
+{
+	use itemReviewedTrait, negativeNotesTrait, positiveNotesTrait,
+		reviewBodyTrait, reviewRatingTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/WebPage/AboutPage.php b/inc/managers/SEO/render/Thing/CreativeWork/WebPage/AboutPage.php
new file mode 100644
index 0000000..8580a9c
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/WebPage/AboutPage.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\CreativeWork\WebPage;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class AboutPage extends WebPage
+{
+
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/WebPage/CheckoutPage.php b/inc/managers/SEO/render/Thing/CreativeWork/WebPage/CheckoutPage.php
new file mode 100644
index 0000000..3c4ed91
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/WebPage/CheckoutPage.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\CreativeWork\WebPage;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+
+class CheckoutPage extends WebPage
+{
+
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/WebPage/CollectionPage.php b/inc/managers/SEO/render/Thing/CreativeWork/WebPage/CollectionPage.php
new file mode 100644
index 0000000..43be137
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/WebPage/CollectionPage.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\CreativeWork\WebPage;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+use JVBase\managers\SEO\render\Traits\_Properties\breadcrumbTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\lastReviewedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\primaryImageOfPageTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\relatedLinkTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\reviewedByTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\significantLinkTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+
+class CollectionPage extends WebPage
+{
+
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/WebPage/ContactPage.php b/inc/managers/SEO/render/Thing/CreativeWork/WebPage/ContactPage.php
new file mode 100644
index 0000000..b881975
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/WebPage/ContactPage.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\CreativeWork\WebPage;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+
+class ContactPage extends WebPage
+{
+
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/WebPage/FAQPage.php b/inc/managers/SEO/render/Thing/CreativeWork/WebPage/FAQPage.php
new file mode 100644
index 0000000..7f382aa
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/WebPage/FAQPage.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\CreativeWork\WebPage;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+
+class FAQPage extends WebPage
+{
+
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/WebPage/ItemPage.php b/inc/managers/SEO/render/Thing/CreativeWork/WebPage/ItemPage.php
new file mode 100644
index 0000000..fb8a85a
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/WebPage/ItemPage.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\CreativeWork\WebPage;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+
+class ItemPage extends WebPage
+{
+
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/WebPage/MedicalWebPage.php b/inc/managers/SEO/render/Thing/CreativeWork/WebPage/MedicalWebPage.php
new file mode 100644
index 0000000..63f2b3d
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/WebPage/MedicalWebPage.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\CreativeWork\WebPage;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+
+class MedicalWebPage extends WebPage
+{
+	//medicalAudience
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/WebPage/ProfilePage.php b/inc/managers/SEO/render/Thing/CreativeWork/WebPage/ProfilePage.php
new file mode 100644
index 0000000..82601de
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/WebPage/ProfilePage.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\CreativeWork\WebPage;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+use JVBase\managers\SEO\render\Traits\_Properties\breadcrumbTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\lastReviewedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\primaryImageOfPageTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\relatedLinkTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\reviewedByTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\significantLinkTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+
+class ProfilePage extends CreativeWork
+{
+	use breadcrumbTrait, lastReviewedTrait, primaryImageOfPageTrait,
+		relatedLinkTrait, reviewedByTrait, significantLinkTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/WebPage/QAPage.php b/inc/managers/SEO/render/Thing/CreativeWork/WebPage/QAPage.php
new file mode 100644
index 0000000..97baf10
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/WebPage/QAPage.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\CreativeWork\WebPage;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+
+class QAPage extends WebPage
+{
+
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/WebPage/RealEstateListing.php b/inc/managers/SEO/render/Thing/CreativeWork/WebPage/RealEstateListing.php
new file mode 100644
index 0000000..6adc8b0
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/WebPage/RealEstateListing.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\CreativeWork\WebPage;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+
+class RealEstateListing extends WebPage
+{
+	//datePosted
+	//leaseLength
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/WebPage/SearchResultsPage.php b/inc/managers/SEO/render/Thing/CreativeWork/WebPage/SearchResultsPage.php
new file mode 100644
index 0000000..a030907
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/WebPage/SearchResultsPage.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\CreativeWork\WebPage;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+
+class SearchResultsPage extends WebPage
+{
+
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/WebPage/WebPage.php b/inc/managers/SEO/render/Thing/CreativeWork/WebPage/WebPage.php
new file mode 100644
index 0000000..eb833af
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/WebPage/WebPage.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\CreativeWork\WebPage;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+use JVBase\managers\SEO\render\Traits\_Properties\breadcrumbTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\lastReviewedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\primaryImageOfPageTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\relatedLinkTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\reviewedByTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\significantLinkTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+
+class WebPage extends CreativeWork
+{
+	use breadcrumbTrait, lastReviewedTrait, primaryImageOfPageTrait,
+		relatedLinkTrait, reviewedByTrait, significantLinkTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/WebPage/_setup.php b/inc/managers/SEO/render/Thing/CreativeWork/WebPage/_setup.php
new file mode 100644
index 0000000..05e2f53
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/WebPage/_setup.php
@@ -0,0 +1,13 @@
+<?php
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/WebPage/WebPage.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/WebPage/AboutPage.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/WebPage/CheckoutPage.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/WebPage/CollectionPage.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/WebPage/ContactPage.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/WebPage/FAQPage.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/WebPage/ItemPage.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/WebPage/MedicalWebPage.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/WebPage/ProfilePage.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/WebPage/QAPage.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/WebPage/RealEstateListing.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/WebPage/SearchResultsPage.php');
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/WebSite.php b/inc/managers/SEO/render/Thing/CreativeWork/WebSite.php
new file mode 100644
index 0000000..7bf0540
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/WebSite.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\CreativeWork;
+
+
+use JVBase\managers\SEO\render\Traits\_Properties\issnTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class WebSite extends CreativeWork
+{
+	use issnTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/CreativeWork/_setup.php b/inc/managers/SEO/render/Thing/CreativeWork/_setup.php
new file mode 100644
index 0000000..5d7b483
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/CreativeWork/_setup.php
@@ -0,0 +1,19 @@
+<?php
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/CreativeWork.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/DefinedTermSet.php');
+
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/CategoryCodeSet.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/Certification.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/Clip.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/EducationalOccupationalCredential.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/HowTo.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/HowToSection.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/HowToStep.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/Menu.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/MenuSection.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/MusicRecording.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/Photograph.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/Review.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/WebSite.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/MediaObject/_setup.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/WebPage/_setup.php');
diff --git a/inc/managers/SEO/render/Thing/Event/Event.php b/inc/managers/SEO/render/Thing/Event/Event.php
new file mode 100644
index 0000000..e64428c
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Event/Event.php
@@ -0,0 +1,54 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Event;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Properties\aboutTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\aggregateRatingTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\attendeeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\audienceTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\composerTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\contributorTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\directorTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\doorTimeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\durationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\endDateTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\funderTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\isAccessibleForFreeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\keywordsTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\languageTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\locationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\maximumAttendeeCapacityTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\maximumPhysicalAttendeeCapacityTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\maximumVirtualAttendeeCapacityTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\offersTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\organizerTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\performerTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\previousStartDateTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\recordedInTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\remainingAttendeeCapacityTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\reviewTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\sponsorTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\startDateTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\subEventTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\superEventTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\translatorTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\typicalAgeRangeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\workFeaturedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\workPerformedTrait;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Event extends Thing
+{
+	use aboutTrait, aggregateRatingTrait, attendeeTrait, audienceTrait,
+		composerTrait, contributorTrait, directorTrait, doorTimeTrait, durationTrait,
+		endDateTrait, funderTrait, languageTrait, isAccessibleForFreeTrait, keywordsTrait,
+		locationTrait, maximumAttendeeCapacityTrait, maximumPhysicalAttendeeCapacityTrait,
+		maximumVirtualAttendeeCapacityTrait, offersTrait, organizerTrait, performerTrait,
+		previousStartDateTrait, recordedInTrait, remainingAttendeeCapacityTrait, reviewTrait,
+		sponsorTrait, startDateTrait, subEventTrait, superEventTrait, translatorTrait, typicalAgeRangeTrait,
+		workFeaturedTrait, workPerformedTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Event/_setup.php b/inc/managers/SEO/render/Thing/Event/_setup.php
new file mode 100644
index 0000000..f940337
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Event/_setup.php
@@ -0,0 +1,2 @@
+<?php
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Event/Event.php');
diff --git a/inc/managers/SEO/render/Thing/Intangible/AggregateOffer.php b/inc/managers/SEO/render/Thing/Intangible/AggregateOffer.php
new file mode 100644
index 0000000..b4ea926
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/AggregateOffer.php
@@ -0,0 +1,33 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible;
+
+use JVBase\managers\SEO\render\DataType\Date;
+use JVBase\managers\SEO\render\DataType\DateTime;
+use JVBase\managers\SEO\render\DataType\Time;
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\AdultOrientedEnumeration;
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\BusinessFunction;
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\DeliveryMethod;
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\ItemAvailability;
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\OfferItemCondition;
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\PhysicalActivityCategory;
+use JVBase\managers\SEO\render\Thing\Intangible\Rating\AggregateRating;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\GeoShape;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\PriceSpecification;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\PropertyValue;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
+use JVBase\managers\SEO\render\Thing\Place\AdministrativeArea\AdministrativeArea;
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Properties\highPriceTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\lowPriceTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\offerCountTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\offersTrait;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class AggregateOffer extends Offer {
+	use highPriceTrait, lowPriceTrait, offerCountTrait, offersTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/Audience.php b/inc/managers/SEO/render/Thing/Intangible/Audience.php
new file mode 100644
index 0000000..a3472ea
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Audience.php
@@ -0,0 +1,16 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Properties\audienceTypeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\geographicAreaTrait;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Audience extends Thing
+{
+	use audienceTypeTrait, geographicAreaTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/Brand/Brand.php b/inc/managers/SEO/render/Thing/Intangible/Brand/Brand.php
new file mode 100644
index 0000000..71ec02a
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Brand/Brand.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\Intangible\Brand;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Properties\aggregateRatingTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\logoTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\reviewTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\sloganTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Brand extends Thing
+{
+	use logoTrait, reviewTrait, aggregateRatingTrait, sloganTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/Brand/_setup.php b/inc/managers/SEO/render/Thing/Intangible/Brand/_setup.php
new file mode 100644
index 0000000..9f21bc6
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Brand/_setup.php
@@ -0,0 +1,2 @@
+<?php
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Brand/Brand.php');
diff --git a/inc/managers/SEO/render/Thing/Intangible/CategoryCode.php b/inc/managers/SEO/render/Thing/Intangible/CategoryCode.php
new file mode 100644
index 0000000..b1fc239
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/CategoryCode.php
@@ -0,0 +1,14 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CategoryCodeSet;
+use JVBase\managers\SEO\render\Traits\_Properties\codeValueTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\inCodeSetTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class CategoryCode extends DefinedTerm {
+	use codeValueTrait, inCodeSetTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/ContactPoint/PostalAddress.php b/inc/managers/SEO/render/Thing/Intangible/ContactPoint/PostalAddress.php
new file mode 100644
index 0000000..c4cdebe
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/ContactPoint/PostalAddress.php
@@ -0,0 +1,20 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\ContactPoint;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Properties\addressCountryTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\addressLocalityTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\addressRegionTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\extendedAddressTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\postalCodeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\postOfficeBoxNumberTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\streetAddressTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class PostalAddress  extends Thing{
+	use addressCountryTrait, addressLocalityTrait, addressRegionTrait, extendedAddressTrait,
+		postOfficeBoxNumberTrait, postalCodeTrait, streetAddressTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/ContactPoint/_setup.php b/inc/managers/SEO/render/Thing/Intangible/ContactPoint/_setup.php
new file mode 100644
index 0000000..1ae10ab
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/ContactPoint/_setup.php
@@ -0,0 +1,2 @@
+<?php
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/ContactPoint/PostalAddress.php');
diff --git a/inc/managers/SEO/render/Thing/Intangible/DefinedTerm.php b/inc/managers/SEO/render/Thing/Intangible/DefinedTerm.php
new file mode 100644
index 0000000..023d1c6
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/DefinedTerm.php
@@ -0,0 +1,17 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\DefinedTermSet;
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Properties\aboutTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\inDefinedTermSetTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\termCodeTrait;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class DefinedTerm extends Thing {
+	use aboutTrait, inDefinedTermSetTrait, termCodeTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/Demand.php b/inc/managers/SEO/render/Thing/Intangible/Demand.php
new file mode 100644
index 0000000..88b3a67
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Demand.php
@@ -0,0 +1,36 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Properties\acceptedPaymentMethodTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\advancedBookingRequirementTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\areaServedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\availabilityEndsTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\availabilityStartsTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\availabilityTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\availableAtOrFromTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\availableDeliveryMethodTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\businessFunctionTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\deliveryLeadTimeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\inventoryLevelTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\itemOfferedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceSpecificationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\sellerTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\serialNumberTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\skuTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\validFromTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\validThroughTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\warrantyTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Demand extends Thing {
+	use acceptedPaymentMethodTrait,advancedBookingRequirementTrait, areaServedTrait, availabilityTrait,
+		availabilityEndsTrait, availabilityStartsTrait, availableAtOrFromTrait, availableDeliveryMethodTrait,
+		businessFunctionTrait, deliveryLeadTimeTrait,
+		inventoryLevelTrait, itemOfferedTrait, priceSpecificationTrait,
+		sellerTrait, serialNumberTrait, skuTrait, validFromTrait, validThroughTrait,
+		warrantyTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/EntryPoint.php b/inc/managers/SEO/render/Thing/Intangible/EntryPoint.php
new file mode 100644
index 0000000..c5a365e
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/EntryPoint.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\DefinedTermSet;
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\DigitalPlatformEnumeration;
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Properties\actionPlatformTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\contentTypeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\encodingTypeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\httpMethodTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\urlTemplateTrait;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class EntryPoint extends Thing {
+	use actionPlatformTrait, contentTypeTrait, encodingTypeTrait,
+		httpMethodTrait, urlTemplateTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/Enumeration/ActionStatusType.php b/inc/managers/SEO/render/Thing/Intangible/Enumeration/ActionStatusType.php
new file mode 100644
index 0000000..d73a5e4
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Enumeration/ActionStatusType.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
+use JVBase\managers\SEO\render\Thing\Thing;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class ActionStatusType extends StatusEnumeration {
+	protected string $status;
+	protected array $allowedStatus = [
+		'active' => 'https://schema.org/ActiveActionStatus',
+		'completed' => 'https://schema.org/CompletedActionStatus',
+		'failed'	=> 'https://schema.org/FailedActionStatus',
+		'potential'	=> 'https://schema.org/PotentialActionStatus'
+	];
+}
+
diff --git a/inc/managers/SEO/render/Thing/Intangible/Enumeration/AdultOrientedEnumeration.php b/inc/managers/SEO/render/Thing/Intangible/Enumeration/AdultOrientedEnumeration.php
new file mode 100644
index 0000000..971852b
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Enumeration/AdultOrientedEnumeration.php
@@ -0,0 +1,25 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class AdultOrientedEnumeration extends Enumeration {
+	protected string $consideration;
+	protected array $allowedConsideration = [
+		'AlcoholConsideration'	=> 'https://schema.org/AlcoholConsideration',
+		'DangerousGoodConsideration'=> 'https://schema.org/DangerousGoodConsideration',
+		'HealthcareConsideration'	=> 'https://schema.org/HealthcareConsideration',
+		'NarcoticConsideration'	=> 'https://schema.org/NarcoticConsideration',
+		'ReducedRelevanceForChildrenConsideration'=>'https://schema.org/ReducedRelevanceForChildrenConsideration',
+		'SexualContentConsideration'=> 'https://schema.org/SexualContentConsideration',
+		'TobaccoNicotineConsideration' => 'https://schema.org/TobaccoNicotineConsideration',
+		'UnclassifiedAdultConsideration' => 'https://schema.org/UnclassifiedAdultConsideration',
+		'ViolenceConsideration' => 'https://schema.org/ViolenceConsideration',
+		'WeaponConsideration'	=> 'https://schema.org/WeaponConsideration'
+	];
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/Enumeration/BusinessEntityType.php b/inc/managers/SEO/render/Thing/Intangible/Enumeration/BusinessEntityType.php
new file mode 100644
index 0000000..c8a203d
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Enumeration/BusinessEntityType.php
@@ -0,0 +1,20 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class BusinessEntityType extends Enumeration {
+	protected string $type;
+	protected array $allowedType = [
+		'business'	=> 'http://purl.org/goodrelations/v1#Business',
+		'endUser'	=> 'http://purl.org/goodrelations/v1#Enduser',
+		'publicInstitution'=> 'http://purl.org/goodrelations/v1#PublicInstitution',
+		'reseller'	=> 'http://purl.org/goodrelations/v1#Reseller'
+	];
+}
+
diff --git a/inc/managers/SEO/render/Thing/Intangible/Enumeration/BusinessFunction.php b/inc/managers/SEO/render/Thing/Intangible/Enumeration/BusinessFunction.php
new file mode 100644
index 0000000..d92eb76
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Enumeration/BusinessFunction.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class BusinessFunction extends Enumeration {
+	protected string $businessFunction;
+	protected array $allowedBusinessFunction = [
+		'constructionInstallation' => 'http://purl.org/goodrelations/v1#ConstructionInstallation',
+		'dispose' => 'http://purl.org/goodrelations/v1#Dispose',
+		'lease' => 'http://purl.org/goodrelations/v1#LeaseOut',
+		'maintain' => 'http://purl.org/goodrelations/v1#Maintain',
+		'provideService' => 'http://purl.org/goodrelations/v1#ProvideService',
+		'repair' => 'http://purl.org/goodrelations/v1#Repair',
+		'sell' => 'http://purl.org/goodrelations/v1#Sell',
+		'buy' => 'http://purl.org/goodrelations/v1#Buy'
+	];
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/Enumeration/CertificationStatusEnumeration.php b/inc/managers/SEO/render/Thing/Intangible/Enumeration/CertificationStatusEnumeration.php
new file mode 100644
index 0000000..6274961
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Enumeration/CertificationStatusEnumeration.php
@@ -0,0 +1,17 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class CertificationStatusEnumeration extends Enumeration {
+	protected string $status;
+	protected array $allowedStatus = [
+		'active'	=> 'https://schema.org/CertificationActive',
+		'inactive'	=> 'https://schema.org/CertificationInactive',
+	];
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/Enumeration/ContactPointOption.php b/inc/managers/SEO/render/Thing/Intangible/Enumeration/ContactPointOption.php
new file mode 100644
index 0000000..c7238ce
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Enumeration/ContactPointOption.php
@@ -0,0 +1,14 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class ContactPointOption extends Enumeration {
+	protected string $option;
+	protected array $allowedOption = [
+		'hearingImpaired'	=> 'https://schema.org/HearingImpairedSupported',
+		'tollFree'	=> 'https://schema.org/TollFree',
+	];
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/Enumeration/DayOfWeek.php b/inc/managers/SEO/render/Thing/Intangible/Enumeration/DayOfWeek.php
new file mode 100644
index 0000000..a14d568
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Enumeration/DayOfWeek.php
@@ -0,0 +1,25 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Schedule;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\OpeningHoursSpecification;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\ServicePeriod;
+use JVBase\managers\SEO\render\Thing\Thing;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class DayOfWeek extends Enumeration {
+	protected string $day;
+	protected array $allowedDay = [
+		'monday'	=> 'https://schema.org/Monday',
+		'tuesday'	=> 'https://schema.org/Tuesday',
+		'wednesday'	=> 'https://schema.org/Wednesday',
+		'thursday'	=> 'https://schema.org/Thursday',
+		'friday'	=> 'https://schema.org/Friday',
+		'saturday'	=> 'https://schema.org/Saturday',
+		'sunday'	=> 'https://schema.org/Sunday',
+		'publicHolidays' => 'https://schema.org/PublicHolidays'
+	];
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/Enumeration/DeliveryMethod.php b/inc/managers/SEO/render/Thing/Intangible/Enumeration/DeliveryMethod.php
new file mode 100644
index 0000000..e4dfc98
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Enumeration/DeliveryMethod.php
@@ -0,0 +1,26 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class DeliveryMethod extends Enumeration {
+	protected string $method;
+	protected array $allowedMethod = [
+		'OnSitePickup'	=> 'OnSitePickup',
+		'ParcelService'	=> 'ParcelService',
+		'LockerPickup'	=> 'LockerPickup',
+		'directDownload' =>'http://purl.org/goodrelations/v1#DeliveryModeDirectDownload',
+		'freight' => 'http://purl.org/goodrelations/v1#DeliveryModeFreight',
+		'mail' => 'http://purl.org/goodrelations/v1#DeliveryModeMail',
+		'ownFleet' => 'http://purl.org/goodrelations/v1#DeliveryModeOwnFleet',
+		'pickUp' => 'http://purl.org/goodrelations/v1#DeliveryModePickUp',
+		'DHL' => 'http://purl.org/goodrelations/v1#DHL',
+		'FederalExpress' => 'http://purl.org/goodrelations/v1#FederalExpress',
+		'UPS' => 'http://purl.org/goodrelations/v1#UPS'
+	];
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/Enumeration/DigitalPlatformEnumeration.php b/inc/managers/SEO/render/Thing/Intangible/Enumeration/DigitalPlatformEnumeration.php
new file mode 100644
index 0000000..a335e4b
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Enumeration/DigitalPlatformEnumeration.php
@@ -0,0 +1,20 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class DigitalPlatformEnumeration extends Enumeration {
+	protected string $platform;
+	protected array $allowedPlatform = [
+		'android'	=> 'https://schema.org/AndroidPlatform',
+		'desktopWeb'	=> 'https://schema.org/DesktopWebPlatform',
+		'generic'	=> 'https://schema.org/GenericWebPlatform',
+		'IOS'		=> 'https://schema.org/IOSPlatform',
+		'mobileWeb'	=> 'https://schema.org/MobileWebPlatform'
+	];
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/Enumeration/Enumeration.php b/inc/managers/SEO/render/Thing/Intangible/Enumeration/Enumeration.php
new file mode 100644
index 0000000..25ec360
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Enumeration/Enumeration.php
@@ -0,0 +1,13 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Enumeration extends Thing  {
+}
+
diff --git a/inc/managers/SEO/render/Thing/Intangible/Enumeration/EventStatusType.php b/inc/managers/SEO/render/Thing/Intangible/Enumeration/EventStatusType.php
new file mode 100644
index 0000000..1078ef7
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Enumeration/EventStatusType.php
@@ -0,0 +1,22 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
+use JVBase\managers\SEO\render\Thing\Thing;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class EventStatusType extends StatusEnumeration {
+	protected string $status;
+	protected array $allowedStatus = [
+		'cancelled' => 'https://schema.org/EventCancelled',
+		'movedOnline'=> 'https://schema.org/EventMovedOnline',
+		'postponed'	=> 'https://schema.org/EventPostponed',
+		'rescheduled'=> 'https://schema.org/EventRescheduled',
+		'scheduled'	=> 'https://schema.org/EventScheduled',
+	];
+}
+
diff --git a/inc/managers/SEO/render/Thing/Intangible/Enumeration/GovernmentBenefitsType.php b/inc/managers/SEO/render/Thing/Intangible/Enumeration/GovernmentBenefitsType.php
new file mode 100644
index 0000000..9a7d267
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Enumeration/GovernmentBenefitsType.php
@@ -0,0 +1,20 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class GovernmentBenefitsType extends Enumeration {
+	protected string $benefit;
+	protected array $allowedBenefit = [
+		'basicIncome'	=> 'https://schema.org/BasicIncome',
+		'businessSupport'=> 'https://schema.org/BusinessSupport',
+		'disabilitySupport'=> 'https://schema.org/DisabilitySupport',
+		'healthCare'		=> 'https://schema.org/HealthCare',
+		'oneTimePayment'	=> 'https://schema.org/OneTimePayments',
+		'paidLeave'		=>'https://schema.org/PaidLeave',
+		'parentalSupport'=> 'https://schema.org/ParentalSupport',
+		'unemploymentSupport'=> 'https://schema.org/UnemploymentSupport'
+	];
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/Enumeration/ItemAvailability.php b/inc/managers/SEO/render/Thing/Intangible/Enumeration/ItemAvailability.php
new file mode 100644
index 0000000..979fff9
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Enumeration/ItemAvailability.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class ItemAvailability extends Enumeration {
+	protected string $availability;
+	protected array $options = ['BackOrder', 'Discontinued', 'InStock', 'InStoreOnly', 'LimitedAvailability', 'MadeToOrder', 'OnlineOnly', 'OutOfStock','PreOrder', 'PreSale', 'Reserved', 'SoldOut'];
+
+	public function setAvailability(string $availability): void {
+		if (!in_array($availability, $this->options)) {
+			error_log('[ItemAvailability] Invalid availability: '.$availability);
+			return;
+		}
+		$this->availability = $availability;
+	}
+
+	public function getAvailability(): string {
+		return $this->availability;
+	}
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/Enumeration/ItemListOrderType.php b/inc/managers/SEO/render/Thing/Intangible/Enumeration/ItemListOrderType.php
new file mode 100644
index 0000000..8a1cad7
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Enumeration/ItemListOrderType.php
@@ -0,0 +1,15 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class ItemListOrderType extends Enumeration {
+	protected string $condition;
+	protected array $allowedCondition = [
+		'ascending'	=> 'https://schema.org/ItemListOrderAscending',
+		'descending'	=> 'https://schema.org/ItemListOrderDescending',
+		'unordered'	=> 'https://schema.org/ItemListUnordered'
+	];
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/Enumeration/OfferItemCondition.php b/inc/managers/SEO/render/Thing/Intangible/Enumeration/OfferItemCondition.php
new file mode 100644
index 0000000..195ceea
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Enumeration/OfferItemCondition.php
@@ -0,0 +1,16 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class OfferItemCondition extends Enumeration {
+	protected string $condition;
+	protected array $allowedCondition = [
+		'damaged'	=> 'https://schema.org/DamagedCondition',
+		'new'	=> 'https://schema.org/NewCondition',
+		'refurbished'	=> 'https://schema.org/RefurbishedCondition',
+		'used'	=> 'https://schema.org/UsedCondition'
+	];
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/Enumeration/OrderStatusType.php b/inc/managers/SEO/render/Thing/Intangible/Enumeration/OrderStatusType.php
new file mode 100644
index 0000000..d94bc52
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Enumeration/OrderStatusType.php
@@ -0,0 +1,25 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
+use JVBase\managers\SEO\render\Thing\Thing;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class OrderStatusType extends StatusEnumeration {
+	protected string $status;
+	protected array $allowedStatus = [
+		'cancelled' => 'https://schema.org/OrderCancelled',
+		'delivered'	=> 'https://schema.org/OrderDelivered',
+		'inTransit'	=> 'https://schema.org/OrderInTransit',
+		'paymentDue'=> 'https://schema.org/OrderPaymentDue',
+		'pickupAvailable'=> 'https://schema.org/OrderPickupAvailable',
+		'problem'	=> 'https://schema.org/OrderProblem',
+		'processing'=> 'https://schema.org/OrderProcessing',
+		'returned'	=> 'https://schema.org/OrderReturned'
+	];
+}
+
diff --git a/inc/managers/SEO/render/Thing/Intangible/Enumeration/PhysicalActivityCategory.php b/inc/managers/SEO/render/Thing/Intangible/Enumeration/PhysicalActivityCategory.php
new file mode 100644
index 0000000..3146e45
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Enumeration/PhysicalActivityCategory.php
@@ -0,0 +1,19 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class PhysicalActivityCategory extends Enumeration {
+	protected string $activity;
+	protected array $allowedActivity = [
+		'aerobic'	=> 'https://schema.org/AerobicActivity',
+		'anaerobic'	=> 'https://schema.org/AnaerobicActivity',
+		'balance'	=> 'https://schema.org/Balance',
+		'flexibility'=> 'https://schema.org/Flexibility',
+		'leisure'	=> 'https://schema.org/LeisureTimeActivity',
+		'occupational'=> 'https://schema.org/OccupationalActivity',
+		'strength'	=> 'https://schema.org/StrengthTraining'
+	];
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/Enumeration/QualitativeValue.php b/inc/managers/SEO/render/Thing/Intangible/Enumeration/QualitativeValue.php
new file mode 100644
index 0000000..0143234
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Enumeration/QualitativeValue.php
@@ -0,0 +1,48 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration;
+
+use JVBase\managers\SEO\render\Thing\Intangible\DefinedTerm;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\PropertyValue;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\StructuredValue;
+use JVBase\managers\SEO\render\Traits\_Properties\additionalPropertyTrait;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class QualitativeValue extends Enumeration {
+	use additionalPropertyTrait;
+
+	/**
+	 * @var QualitativeValue This ordering relation for qualitative values indicates that the subject is equal to the object.
+	 */
+	protected QualitativeValue $equal;
+	/**
+	 * @var QualitativeValue This ordering relation for qualitative values indicates that the subject is greater than the object.
+	 */
+	protected QualitativeValue $greater;
+	/**
+	 * @var QualitativeValue This ordering relation for qualitative values indicates that the subject is greater than or equal to the object.
+	 */
+	protected QualitativeValue $greaterOrEqual;
+	/**
+	 * @var QualitativeValue This ordering relation for qualitative values indicates that the subject is lesser than the object.
+	 */
+	protected QualitativeValue $lesser;
+	/**
+	 * @var QualitativeValue This ordering relation for qualitative values indicates that the subject is lesser than or equal to the object.
+	 */
+	protected QualitativeValue $lesserOrEqual;
+	/**
+	 * @var QualitativeValue This ordering relation for qualitative values indicates that the subject is not equal to the object.
+	 */
+	protected QualitativeValue $nonEqual;
+	/**
+	 * @var DefinedTerm|Enumeration|PropertyValue|QualitativeValue|QuantitativeValue|StructuredValue|string
+	 *A secondary value that provides additional information on the original value, e.g. a reference temperature or a type of measurement.
+	 */
+	protected DefinedTerm|Enumeration|PropertyValue|QualitativeValue|QuantitativeValue|StructuredValue|string $valueReference;
+}
+
diff --git a/inc/managers/SEO/render/Thing/Intangible/Enumeration/RefundTypeEnumeration.php b/inc/managers/SEO/render/Thing/Intangible/Enumeration/RefundTypeEnumeration.php
new file mode 100644
index 0000000..794d893
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Enumeration/RefundTypeEnumeration.php
@@ -0,0 +1,15 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class RefundTypeEnumeration extends Enumeration {
+	protected string $refundType;
+	protected array $allowedRefundType = [
+		'exchange'	=> 'https://schema.org/ExchangeRefund',
+		'full' => 'https://schema.org/FullRefund',
+		'credit' => 'https://schema.org/StoreCreditRefund'
+	];
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/Enumeration/RestrictedDiet.php b/inc/managers/SEO/render/Thing/Intangible/Enumeration/RestrictedDiet.php
new file mode 100644
index 0000000..a0561af
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Enumeration/RestrictedDiet.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class RestrictedDiet extends Enumeration {
+	protected string $type = "RestrictedDiet";
+	protected string $diet;
+	protected array $allowedDiet = [
+		'DiabeticDiet' => 'https://schema.org/DiabeticDiet',
+		'GlutenFreeDiet' => 'https://schema.org/GlutenFreeDiet',
+		'HalalDiet' => 'https://schema.org/HalalDiet',
+		'HinduDiet' => 'https://schema.org/HinduDiet',
+		'KosherDiet' => 'https://schema.org/KosherDiet',
+		'LowCalorieDiet' => 'https://schema.org/LowCalorieDiet',
+		'LowFatDiet' => 'https://schema.org/LowFatDiet',
+		'LowLactoseDiet' => 'https://schema.org/LowLactoseDiet',
+		'LowSaltDiet' => 'https://schema.org/LowSaltDiet',
+		'VeganDiet' => 'https://schema.org/VeganDiet',
+		'VegetarianDiet' => 'https://schema.org/VegetarianDiet',
+	];
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/Enumeration/SizeGroupEnumeration.php b/inc/managers/SEO/render/Thing/Intangible/Enumeration/SizeGroupEnumeration.php
new file mode 100644
index 0000000..cfa294c
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Enumeration/SizeGroupEnumeration.php
@@ -0,0 +1,15 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
+use JVBase\managers\SEO\render\Thing\Thing;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class SizeGroupEnumeration extends Enumeration {
+
+}
+
diff --git a/inc/managers/SEO/render/Thing/Intangible/Enumeration/SizeSpecification.php b/inc/managers/SEO/render/Thing/Intangible/Enumeration/SizeSpecification.php
new file mode 100644
index 0000000..d101c58
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Enumeration/SizeSpecification.php
@@ -0,0 +1,35 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
+use JVBase\managers\SEO\render\Traits\_Properties\hasMeasurementTrait;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class SizeSpecification extends QualitativeValue {
+	use hasMeasurementTrait;
+	/**
+	 * @var SizeGroupEnumeration The size group (also known as "size type") for a product's size. Size groups are common in the fashion industry to define size segments and suggested audiences for wearable products. Multiple values can be combined, for example "men's big and tall", "petite maternity" or "regular".
+	 */
+	protected SizeGroupEnumeration $sizeGroup;
+	/**
+	 * @var SizeSystemEnumeration|string The size system used to identify a product's size. Typically either a standard (for example, "GS1" or "ISO-EN13402"), country code (for example "US" or "JP"), or a measuring system (for example "Metric" or "Imperial").
+	 */
+	protected SizeSystemEnumeration|string $sizeSystem;
+	/**
+	 * @var QuantitativeValue The age or age range for the intended audience or person, for example 3-12 months for infants, 1-5 years for toddlers.
+	 */
+	protected QuantitativeValue $suggestedAge;
+	/**
+	 * @var string The suggested gender of the intended person or audience, for example "male", "female", or "unisex".
+	 */
+	protected string $suggestedGender;
+	/**
+	 * @var QuantitativeValue A suggested range of body measurements for the intended audience or person, for example inseam between 32 and 34 inches or height between 170 and 190 cm. Typically found on a size chart for wearable products.
+	 */
+	protected QuantitativeValue $suggestedMeasurement;
+}
+
diff --git a/inc/managers/SEO/render/Thing/Intangible/Enumeration/SizeSystemEnumeration.php b/inc/managers/SEO/render/Thing/Intangible/Enumeration/SizeSystemEnumeration.php
new file mode 100644
index 0000000..f4d6f91
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Enumeration/SizeSystemEnumeration.php
@@ -0,0 +1,20 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
+use JVBase\managers\SEO\render\Thing\Thing;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class SizeSystemEnumeration extends Enumeration {
+	protected string $system;
+	protected array $allowedSystem = [
+		'imperial' => 'https://schema.org/SizeSystemImperial',
+		'metric' => 'https://schema.org/SizeSystemMetric',
+	];
+
+}
+
diff --git a/inc/managers/SEO/render/Thing/Intangible/Enumeration/StatusEnumeration.php b/inc/managers/SEO/render/Thing/Intangible/Enumeration/StatusEnumeration.php
new file mode 100644
index 0000000..dc1e52c
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Enumeration/StatusEnumeration.php
@@ -0,0 +1,15 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
+use JVBase\managers\SEO\render\Thing\Thing;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class StatusEnumeration extends Enumeration {
+	protected string $status;
+}
+
diff --git a/inc/managers/SEO/render/Thing/Intangible/Enumeration/WarrantyScope.php b/inc/managers/SEO/render/Thing/Intangible/Enumeration/WarrantyScope.php
new file mode 100644
index 0000000..9d905a6
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Enumeration/WarrantyScope.php
@@ -0,0 +1,20 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
+use JVBase\managers\SEO\render\Thing\Thing;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class WarrantyScope extends Enumeration {
+	protected string $scope;
+	protected array $allowedScope = [
+		'labor'	=> 'http://purl.org/goodrelations/v1#Labor-BringIn',
+		'partsLaborBring' => 'http://purl.org/goodrelations/v1#PartsAndLabor-BringIn',
+		'partsLaborPick'	=> 'http://purl.org/goodrelations/v1#PartsAndLabor-PickUp'
+	];
+}
+
diff --git a/inc/managers/SEO/render/Thing/Intangible/Enumeration/WearableSizeGroupEnumeration.php b/inc/managers/SEO/render/Thing/Intangible/Enumeration/WearableSizeGroupEnumeration.php
new file mode 100644
index 0000000..ebcc9bd
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Enumeration/WearableSizeGroupEnumeration.php
@@ -0,0 +1,33 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\Enumeration;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
+use JVBase\managers\SEO\render\Thing\Thing;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class WearableSizeGroupEnumeration extends Enumeration {
+	protected string $sizeGroup;
+	protected array $allowedSizeGroup = [
+		'big'	=> 'https://schema.org/WearableSizeGroupBig',
+		'boys'	=> 'https://schema.org/WearableSizeGroupBoys',
+		'extraShort'=> 'https://schema.org/WearableSizeGroupExtraShort',
+		'extraTall'=> 'https://schema.org/WearableSizeGroupExtraTall',
+		'girls'	=> 'https://schema.org/WearableSizeGroupGirls',
+		'husky'	=> 'https://schema.org/WearableSizeGroupHusky',
+		'infants'=> 'https://schema.org/WearableSizeGroupInfants',
+		'juniors'=> 'https://schema.org/WearableSizeGroupJuniors',
+		'maternity'=> 'https://schema.org/WearableSizeGroupMaternity',
+		'mens'	=> 'https://schema.org/WearableSizeGroupMens',
+		'misses'=> 'https://schema.org/WearableSizeGroupMisses',
+		'petite'=> 'https://schema.org/WearableSizeGroupPetite',
+		'plus'	=> 'https://schema.org/WearableSizeGroupPlus',
+		'short'=> 'https://schema.org/WearableSizeGroupRegular',
+		'tall'	=> 'https://schema.org/WearableSizeGroupTall',
+		'womens' => 'https://schema.org/WearableSizeGroupWomens'
+	];
+}
+
diff --git a/inc/managers/SEO/render/Thing/Intangible/Enumeration/_setup.php b/inc/managers/SEO/render/Thing/Intangible/Enumeration/_setup.php
new file mode 100644
index 0000000..be6f6f4
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Enumeration/_setup.php
@@ -0,0 +1,27 @@
+<?php
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Enumeration/Enumeration.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Enumeration/AdultOrientedEnumeration.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Enumeration/BusinessEntityType.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Enumeration/BusinessFunction.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Enumeration/CertificationStatusEnumeration.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Enumeration/ContactPointOption.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Enumeration/DayOfWeek.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Enumeration/DeliveryMethod.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Enumeration/DigitalPlatformEnumeration.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Enumeration/GovernmentBenefitsType.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Enumeration/ItemAvailability.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Enumeration/ItemListOrderType.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Enumeration/OfferItemCondition.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Enumeration/PhysicalActivityCategory.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Enumeration/QualitativeValue.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Enumeration/RefundTypeEnumeration.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Enumeration/RestrictedDiet.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Enumeration/SizeGroupEnumeration.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Enumeration/SizeSpecification.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Enumeration/SizeSystemEnumeration.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Enumeration/StatusEnumeration.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Enumeration/ActionStatusType.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Enumeration/EventStatusType.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Enumeration/OrderStatusType.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Enumeration/WarrantyScope.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Enumeration/WearableSizeGroupEnumeration.php');
diff --git a/inc/managers/SEO/render/Thing/Intangible/Grant.php b/inc/managers/SEO/render/Thing/Intangible/Grant.php
new file mode 100644
index 0000000..d6a5ca2
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Grant.php
@@ -0,0 +1,15 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Properties\fundedItemTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\funderTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\sponsorTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Grant extends Thing {
+	use fundedItemTrait, funderTrait, sponsorTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/HowToItem.php b/inc/managers/SEO/render/Thing/Intangible/HowToItem.php
new file mode 100644
index 0000000..bd1d490
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/HowToItem.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\Intangible;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Properties\requiredQuantityTrait;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class HowToItem extends Thing
+{
+	use requiredQuantityTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/HowToSupply.php b/inc/managers/SEO/render/Thing/Intangible/HowToSupply.php
new file mode 100644
index 0000000..fda37ae
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/HowToSupply.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\Intangible;
+
+use JVBase\managers\SEO\render\Traits\_Properties\estimateCostTrait;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class HowToSupply extends HowToItem
+{
+	use estimateCostTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/HowToTool.php b/inc/managers/SEO/render/Thing/Intangible/HowToTool.php
new file mode 100644
index 0000000..fffd11a
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/HowToTool.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\Intangible;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\MonetaryAmount;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Properties\requiredQuantityTrait;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class HowToTool extends HowToItem
+{
+	use requiredQuantityTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/ItemList/BreadcrumbList.php b/inc/managers/SEO/render/Thing/Intangible/ItemList/BreadcrumbList.php
new file mode 100644
index 0000000..d5b63d3
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/ItemList/BreadcrumbList.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\Intangible\ItemList;
+
+use JVBase\managers\SEO\render\Traits\_Properties\aggregateElementTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\itemListElementTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\itemListOrderTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\numberOfItemsTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+
+class BreadcrumbList extends ItemList
+{
+	use aggregateElementTrait, itemListElementTrait, itemListOrderTrait, numberOfItemsTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/ItemList/ItemList.php b/inc/managers/SEO/render/Thing/Intangible/ItemList/ItemList.php
new file mode 100644
index 0000000..c0e20bd
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/ItemList/ItemList.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\Intangible\ItemList;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Properties\aggregateElementTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\itemListElementTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\itemListOrderTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\numberOfItemsTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class ItemList extends Thing
+{
+	use aggregateElementTrait, itemListElementTrait, itemListOrderTrait, numberOfItemsTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/ItemList/_setup.php b/inc/managers/SEO/render/Thing/Intangible/ItemList/_setup.php
new file mode 100644
index 0000000..22a789d
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/ItemList/_setup.php
@@ -0,0 +1,4 @@
+<?php
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/ItemList/ItemList.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/ItemList/BreadcrumbList.php');
+
diff --git a/inc/managers/SEO/render/Thing/Intangible/Language.php b/inc/managers/SEO/render/Thing/Intangible/Language.php
new file mode 100644
index 0000000..eca6562
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Language.php
@@ -0,0 +1,12 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible;
+
+
+use JVBase\managers\SEO\render\Thing\Thing;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Language extends Thing {
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/ListItem.php b/inc/managers/SEO/render/Thing/Intangible/ListItem.php
new file mode 100644
index 0000000..d2dcd3e
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/ListItem.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\Intangible;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Properties\itemTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\nextItemTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\positionTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\previousItemTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class ListItem extends Thing
+{
+	use itemTrait, nextItemTrait, previousItemTrait, positionTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/MenuItem.php b/inc/managers/SEO/render/Thing/Intangible/MenuItem.php
new file mode 100644
index 0000000..be9e472
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/MenuItem.php
@@ -0,0 +1,20 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible;
+
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\MenuSection;
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\RestrictedDiet;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\NutritionalInformation;
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Properties\menuAddOnTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\nutritionTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\offersTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\suitableForDietTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class MenuItem  extends Thing {
+	use menuAddOnTrait, nutritionTrait, offersTrait, suitableForDietTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/MerchantReturnPolicy.php b/inc/managers/SEO/render/Thing/Intangible/MerchantReturnPolicy.php
new file mode 100644
index 0000000..e7af2bb
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/MerchantReturnPolicy.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\Intangible;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Properties\additionalPropertyTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\applicableCountryTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\inStoreReturnsOfferedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\itemConditionTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\merchantReturnDaysTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\merchantReturnLinkTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\refundTypeTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class MerchantReturnPolicy extends Thing
+{
+	use additionalPropertyTrait, applicableCountryTrait,
+		inStoreReturnsOfferedTrait, itemConditionTrait, merchantReturnDaysTrait,
+		merchantReturnLinkTrait, refundTypeTrait;
+	/** TODO And more... not necessary at this point */
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/Occupation.php b/inc/managers/SEO/render/Thing/Intangible/Occupation.php
new file mode 100644
index 0000000..679fee8
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Occupation.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\Intangible;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Properties\educationRequirementsTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\experienceRequirementsTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\occupationalCategoryTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\occupationLocationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\qualificationsTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\responsibilitiesTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\skillsTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Occupation extends Thing
+{
+	use educationRequirementsTrait, experienceRequirementsTrait, occupationLocationTrait,
+		occupationalCategoryTrait, qualificationsTrait, responsibilitiesTrait, skillsTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/OccupationalExperienceRequirements.php b/inc/managers/SEO/render/Thing/Intangible/OccupationalExperienceRequirements.php
new file mode 100644
index 0000000..5a9a492
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/OccupationalExperienceRequirements.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\Intangible;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Properties\monthsOfExperienceTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class OccupationalExperienceRequirements extends Thing
+{
+	use monthsOfExperienceTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/Offer.php b/inc/managers/SEO/render/Thing/Intangible/Offer.php
new file mode 100644
index 0000000..6d63210
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Offer.php
@@ -0,0 +1,66 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Properties\acceptedPaymentMethodTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\additionalPropertyTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\addOnTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\advancedBookingRequirementTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\aggregateRatingTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\areaServedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\availabilityEndsTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\availabilityStartsTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\availabilityTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\availableAtOrFromTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\availableDeliveryMethodTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\businessFunctionTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\categoryTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\checkoutPageURLTemplateTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\deliveryLeadTimeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\eligibleCustomerTypeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\eligibleDurationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\eligibleQuantityTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\eligibleRegionTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\eligibleTransactionVolumeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\gtinTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\hasAdultConsiderationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\hasMeasurementTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\hasMerchantReturnPolicyTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\includesObjectTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\ineligibleLocationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\inventoryLevelTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\isFamilyFriendlyTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\itemConditionTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\itemOfferedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\mobileUrlTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\offeredByTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceCurrencyTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceSpecificationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceValidUntilTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\reviewTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\sellerTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\serialNumberTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\skuTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\validFromTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\validThroughTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\warrantyTrait;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Offer extends Thing {
+	use acceptedPaymentMethodTrait, addOnTrait, additionalPropertyTrait,
+		advancedBookingRequirementTrait, aggregateRatingTrait, areaServedTrait,
+		availabilityTrait, availabilityEndsTrait, availabilityStartsTrait, availableAtOrFromTrait,
+		availableDeliveryMethodTrait, businessFunctionTrait, categoryTrait,
+		checkoutPageURLTemplateTrait, deliveryLeadTimeTrait, eligibleCustomerTypeTrait,
+		eligibleDurationTrait, eligibleQuantityTrait, eligibleRegionTrait, eligibleTransactionVolumeTrait,
+		gtinTrait, hasAdultConsiderationTrait, hasMeasurementTrait, hasMerchantReturnPolicyTrait,
+		includesObjectTrait, ineligibleLocationTrait, inventoryLevelTrait, isFamilyFriendlyTrait,
+		itemConditionTrait, itemOfferedTrait, mobileUrlTrait, offeredByTrait, priceTrait, priceCurrencyTrait,
+		priceSpecificationTrait, priceValidUntilTrait, reviewTrait, sellerTrait, serialNumberTrait, skuTrait,
+		validFromTrait, validThroughTrait, warrantyTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/OfferCatalog.php b/inc/managers/SEO/render/Thing/Intangible/OfferCatalog.php
new file mode 100644
index 0000000..926c607
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/OfferCatalog.php
@@ -0,0 +1,13 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible;
+
+use JVBase\managers\SEO\render\Thing\Intangible\ItemList\ItemList;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class OfferCatalog extends ItemList
+{
+
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/PaymentMethod.php b/inc/managers/SEO/render/Thing/Intangible/PaymentMethod.php
new file mode 100644
index 0000000..2d62cc4
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/PaymentMethod.php
@@ -0,0 +1,34 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible;
+
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\MenuSection;
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\RestrictedDiet;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\NutritionalInformation;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\StructuredValue;
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\ThingSchema;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+/**
+ * Has defined types, but can also use StructuredValue
+ */
+class PaymentMethod  {
+	use ThingSchema;
+	protected StructuredValue|string $method;
+	protected array $allowedMethod = [
+		'bankTransfer'	=> 'http://purl.org/goodrelations/v1#ByBankTransferInAdvance',
+		'invoice'		=> 'http://purl.org/goodrelations/v1#ByInvoice',
+		'cash'			=> 'http://purl.org/goodrelations/v1#Cash',
+		'check'			=> 'http://purl.org/goodrelations/v1#CheckInAdvance',
+		'cod'			=> 'http://purl.org/goodrelations/v1#COD',
+		'directDebit'	=> 'http://purl.org/goodrelations/v1#DirectDebit',
+		'google'		=> 'http://purl.org/goodrelations/v1#GoogleCheckout',
+		'paypal'		=> 'http://purl.org/goodrelations/v1#PayPal',
+		'payswarm'		=> 'http://purl.org/goodrelations/v1#PaySwarm'
+	];
+
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/Quantity/Distance.php b/inc/managers/SEO/render/Thing/Intangible/Quantity/Distance.php
new file mode 100644
index 0000000..d8b90bf
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Quantity/Distance.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\Intangible\Quantity;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Distance extends Thing
+{
+	/**
+	 * @var string <Number> <Length unit of measure>'. E.g., '7 ft'.
+	 */
+	protected string $distance;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/Quantity/Duration.php b/inc/managers/SEO/render/Thing/Intangible/Quantity/Duration.php
new file mode 100644
index 0000000..47a4faf
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Quantity/Duration.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\Intangible\Quantity;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Duration extends Thing
+{
+	/**
+	 * @var string Duration (use ISO 8601 duration format).
+	 */
+	protected string $duration;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/Quantity/Mass.php b/inc/managers/SEO/render/Thing/Intangible/Quantity/Mass.php
new file mode 100644
index 0000000..fadd341
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Quantity/Mass.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\Intangible\Quantity;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Mass extends Thing
+{
+	/**
+	 * @var string <Number> <Mass unit of measure>'. E.g., '7 kg'.
+	 */
+	protected string $mass;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/Quantity/_setup.php b/inc/managers/SEO/render/Thing/Intangible/Quantity/_setup.php
new file mode 100644
index 0000000..57767be
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Quantity/_setup.php
@@ -0,0 +1,4 @@
+<?php
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Quantity/Distance.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Quantity/Duration.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Quantity/Mass.php');
diff --git a/inc/managers/SEO/render/Thing/Intangible/Rating/AggregateRating.php b/inc/managers/SEO/render/Thing/Intangible/Rating/AggregateRating.php
new file mode 100644
index 0000000..1b22f31
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Rating/AggregateRating.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\Intangible\Rating;
+
+use JVBase\managers\SEO\render\Traits\_Properties\itemReviewedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\ratingCountTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\reviewCountTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class AggregateRating extends Rating
+{
+	use itemReviewedTrait, ratingCountTrait, reviewCountTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/Rating/Rating.php b/inc/managers/SEO/render/Thing/Intangible/Rating/Rating.php
new file mode 100644
index 0000000..b4bcfc9
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Rating/Rating.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\Intangible\Rating;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Properties\authorTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\bestRatingTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\ratingValueTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\worstRatingTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Rating extends Thing
+{
+	use authorTrait, bestRatingTrait, ratingValueTrait, worstRatingTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/Rating/_setup.php b/inc/managers/SEO/render/Thing/Intangible/Rating/_setup.php
new file mode 100644
index 0000000..835fe00
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Rating/_setup.php
@@ -0,0 +1,4 @@
+<?php
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Rating/Rating.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Rating/AggregateRating.php');
+
diff --git a/inc/managers/SEO/render/Thing/Intangible/Reservation/FoodEstablishmentReservation.php b/inc/managers/SEO/render/Thing/Intangible/Reservation/FoodEstablishmentReservation.php
new file mode 100644
index 0000000..47c5348
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Reservation/FoodEstablishmentReservation.php
@@ -0,0 +1,14 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\Reservation;
+
+use JVBase\managers\SEO\render\Traits\_Properties\endTimeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\partySizeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\startTimeTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class FoodEstablishmentReservation extends Reservation {
+	use endTimeTrait, partySizeTrait, startTimeTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/Reservation/Reservation.php b/inc/managers/SEO/render/Thing/Intangible/Reservation/Reservation.php
new file mode 100644
index 0000000..4a1a1c9
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Reservation/Reservation.php
@@ -0,0 +1,22 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\Reservation;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Properties\bookingTimeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceCurrencyTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\providerTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\reservationForTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\reservationIdTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\totalPriceTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Reservation  extends Thing {
+	use bookingTimeTrait, priceCurrencyTrait, providerTrait,
+		reservationForTrait, reservationIdTrait, totalPriceTrait;
+//	protected ReservationStatusType $reservationStatus;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/Reservation/_setup.php b/inc/managers/SEO/render/Thing/Intangible/Reservation/_setup.php
new file mode 100644
index 0000000..283a7a0
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Reservation/_setup.php
@@ -0,0 +1,3 @@
+<?php
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Reservation/Reservation.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Reservation/FoodEstablishmentReservation.php');
diff --git a/inc/managers/SEO/render/Thing/Intangible/Schedule.php b/inc/managers/SEO/render/Thing/Intangible/Schedule.php
new file mode 100644
index 0000000..f3f8e32
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Schedule.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\Intangible;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Properties\byDayTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\byMonthDayTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\byMonthTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\byMonthWeekTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\durationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\endDateTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\endTimeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\exceptDateTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\repeatCountTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\repeatFrequencyTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\scheduleTimezoneTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\startDateTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\startTimeTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Schedule extends Thing
+{
+	use byDayTrait, byMonthTrait, byMonthDayTrait, byMonthWeekTrait,
+		durationTrait, endDateTrait, endTimeTrait, exceptDateTrait,
+		repeatCountTrait, repeatFrequencyTrait, scheduleTimezoneTrait,
+		startDateTrait, startTimeTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/Service.php b/inc/managers/SEO/render/Thing/Intangible/Service.php
new file mode 100644
index 0000000..0f7f55d
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/Service.php
@@ -0,0 +1,38 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Properties\aggregateRatingTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\areaServedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\audienceTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\availableChannelTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\awardTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\brandTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\brokerTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\categoryTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\hasCertificationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\hasOfferCatalogTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\hoursAvailableTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\isRelatedToTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\isSimilarToTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\logoTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\offersTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\providerMobilityTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\providerTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\reviewTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\serviceOutputTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\serviceTypeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\sloganTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\termsOfServiceTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Service  extends Thing{
+	use aggregateRatingTrait, areaServedTrait, audienceTrait, availableChannelTrait,
+		awardTrait, brandTrait, brokerTrait, categoryTrait, hasCertificationTrait, hasOfferCatalogTrait,
+		hoursAvailableTrait, isRelatedToTrait, isSimilarToTrait, logoTrait, offersTrait,
+		providerTrait, providerMobilityTrait, reviewTrait, serviceOutputTrait,
+		serviceTypeTrait, sloganTrait, termsOfServiceTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/ServiceChannel.php b/inc/managers/SEO/render/Thing/Intangible/ServiceChannel.php
new file mode 100644
index 0000000..832037e
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/ServiceChannel.php
@@ -0,0 +1,25 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Properties\availableLanguageTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\processingTimeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\providesServiceTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\serviceLocationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\servicePhoneTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\servicePostalAddressTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\serviceSmsNumberTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\serviceUrlTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+/**
+ * A means for accessing a service, e.g. a government office location, web site, or phone number.
+ */
+class ServiceChannel extends Thing{
+	use availableLanguageTrait, processingTimeTrait, providesServiceTrait,
+		serviceLocationTrait, servicePhoneTrait, servicePostalAddressTrait,
+		serviceSmsNumberTrait, serviceUrlTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/StructuredValue/ContactPoint.php b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/ContactPoint.php
new file mode 100644
index 0000000..a9e3b30
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/ContactPoint.php
@@ -0,0 +1,22 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\StructuredValue;
+
+use JVBase\managers\SEO\render\Traits\_Properties\areaServedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\availableLanguageTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\contactOptionTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\contactTypeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\emailTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\faxNumberTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\hoursAvailableTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\productSupportedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\telephoneTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class ContactPoint extends StructuredValue {
+	use areaServedTrait, availableLanguageTrait, contactOptionTrait,
+		contactTypeTrait, emailTrait, faxNumberTrait, hoursAvailableTrait,
+		productSupportedTrait, telephoneTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/StructuredValue/GeoCoordinates.php b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/GeoCoordinates.php
new file mode 100644
index 0000000..41cb589
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/GeoCoordinates.php
@@ -0,0 +1,18 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\StructuredValue;
+
+use JVBase\managers\SEO\render\Traits\_Properties\addressTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\countryTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\elevationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\latitudeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\longitudeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\postalCodeTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class GeoCoordinates extends StructuredValue {
+	use addressTrait, countryTrait, elevationTrait, latitudeTrait, longitudeTrait,
+		postalCodeTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/StructuredValue/GeoShape.php b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/GeoShape.php
new file mode 100644
index 0000000..7c9ea49
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/GeoShape.php
@@ -0,0 +1,20 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\StructuredValue;
+
+use JVBase\managers\SEO\render\Traits\_Properties\addressCountryTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\boxTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\circleTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\elevationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\lineTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\polygonTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\postalAddressTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\postalCodeTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class GeoShape extends StructuredValue {
+	use postalAddressTrait, addressCountryTrait, boxTrait, circleTrait,
+		elevationTrait, lineTrait, polygonTrait, postalCodeTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/StructuredValue/InteractionCounter.php b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/InteractionCounter.php
new file mode 100644
index 0000000..6f02323
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/InteractionCounter.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\Intangible\StructuredValue;
+
+use JVBase\managers\SEO\render\DataType\DateTime;
+use JVBase\managers\SEO\render\DataType\Time;
+use JVBase\managers\SEO\render\Thing\Action;
+use JVBase\managers\SEO\render\Thing\CreativeWork\WebSite;
+use JVBase\managers\SEO\render\Thing\Intangible\ContactPoint\PostalAddress;
+use JVBase\managers\SEO\render\Thing\Intangible\VirtualLocation;
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Properties\endTimeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\interactionServiceTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\interactionTypeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\locationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\startTimeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\userInteractionCountTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class InteractionCounter extends StructuredValue
+{
+	use endTimeTrait, interactionServiceTrait, interactionTypeTrait,
+		locationTrait, startTimeTrait, userInteractionCountTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/StructuredValue/LocationFeatureSpecification.php b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/LocationFeatureSpecification.php
new file mode 100644
index 0000000..ee84819
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/LocationFeatureSpecification.php
@@ -0,0 +1,14 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\StructuredValue;
+
+use JVBase\managers\SEO\render\Traits\_Properties\hoursAvailableTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\validFromTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\validThroughTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class LocationFeatureSpecification extends PropertyValue {
+	use hoursAvailableTrait, validFromTrait, validThroughTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/StructuredValue/MonetaryAmount.php b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/MonetaryAmount.php
new file mode 100644
index 0000000..172d46c
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/MonetaryAmount.php
@@ -0,0 +1,17 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\StructuredValue;
+
+use JVBase\managers\SEO\render\Traits\_Properties\currencyTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\maxValueTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\minValueTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\validFromTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\validThroughTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\valueTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class MonetaryAmount extends StructuredValue {
+	use currencyTrait, maxValueTrait, minValueTrait, validFromTrait, validThroughTrait, valueTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/StructuredValue/NutritionalInformation.php b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/NutritionalInformation.php
new file mode 100644
index 0000000..b518c7a
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/NutritionalInformation.php
@@ -0,0 +1,26 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\StructuredValue;
+
+use JVBase\managers\SEO\render\Traits\_Properties\caloriesTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\carbohydrateContentTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\cholesterolContentTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\fatContentTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\fiberContentTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\proteinContentTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\saturatedFatContentTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\servingSizeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\sodiumContentTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\sugarContentTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\transFatContentTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\unsaturatedFatContentTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class NutritionalInformation extends StructuredValue {
+	use caloriesTrait, carbohydrateContentTrait, cholesterolContentTrait,
+		fatContentTrait, fiberContentTrait, proteinContentTrait, saturatedFatContentTrait,
+		servingSizeTrait, sodiumContentTrait, sugarContentTrait,
+		transFatContentTrait, unsaturatedFatContentTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/StructuredValue/OpeningHoursSpecification.php b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/OpeningHoursSpecification.php
new file mode 100644
index 0000000..56fe960
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/OpeningHoursSpecification.php
@@ -0,0 +1,16 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\StructuredValue;
+
+use JVBase\managers\SEO\render\Traits\_Properties\closesTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\dayOfWeekTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\opensTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\validFromTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\validThroughTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class OpeningHoursSpecification extends StructuredValue {
+	use closesTrait, dayOfWeekTrait, opensTrait, validFromTrait, validThroughTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/StructuredValue/PriceSpecification.php b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/PriceSpecification.php
new file mode 100644
index 0000000..f58293a
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/PriceSpecification.php
@@ -0,0 +1,22 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\StructuredValue;
+
+use JVBase\managers\SEO\render\Traits\_Properties\eligibleQuantityTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\eligibleTransactionVolumeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\maxPriceTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\membershipPointsEarnedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\minPriceTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceCurrencyTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\validFromTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\validThroughTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class PriceSpecification extends StructuredValue {
+	use eligibleQuantityTrait, eligibleTransactionVolumeTrait, maxPriceTrait,
+		membershipPointsEarnedTrait, minPriceTrait, priceTrait, priceCurrencyTrait,
+		validFromTrait, validThroughTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/StructuredValue/PropertyValue.php b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/PropertyValue.php
new file mode 100644
index 0000000..f80a5fc
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/PropertyValue.php
@@ -0,0 +1,16 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\StructuredValue;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Properties\maxValueTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\minValueTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\propertyIDTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\valueTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class PropertyValue extends StructuredValue {
+	use maxValueTrait, minValueTrait, propertyIDTrait, valueTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/StructuredValue/QuantitativeValue.php b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/QuantitativeValue.php
new file mode 100644
index 0000000..6280af6
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/QuantitativeValue.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\StructuredValue;
+
+use JVBase\managers\SEO\render\Traits\_Properties\additionalPropertyTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\maxValueTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\minValueTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\unitCodeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\unitTextTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\valueReferenceTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\valueTrait;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class QuantitativeValue extends StructuredValue
+{
+	use additionalPropertyTrait, maxValueTrait, minValueTrait,
+		unitCodeTrait, unitTextTrait, valueTrait, valueReferenceTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/StructuredValue/ServicePeriod.php b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/ServicePeriod.php
new file mode 100644
index 0000000..a7475b8
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/ServicePeriod.php
@@ -0,0 +1,18 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\StructuredValue;
+
+use JVBase\managers\SEO\render\DataType\Time;
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\DayOfWeek;
+use JVBase\managers\SEO\render\Thing\Intangible\Quantity\Duration;
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Properties\businessDaysTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\cutoffTimeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\durationTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class ServicePeriod extends StructuredValue {
+	use businessDaysTrait, cutoffTimeTrait, durationTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/StructuredValue/StructuredValue.php b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/StructuredValue.php
new file mode 100644
index 0000000..dedf123
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/StructuredValue.php
@@ -0,0 +1,13 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\StructuredValue;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\ThingSchema;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class StructuredValue extends Thing {
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/StructuredValue/TypeAndQuantityNode.php b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/TypeAndQuantityNode.php
new file mode 100644
index 0000000..03ae6d8
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/TypeAndQuantityNode.php
@@ -0,0 +1,17 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\StructuredValue;
+
+use JVBase\managers\SEO\render\Traits\_Properties\amountOfThisGoodTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\businessFunctionTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\typeOfGoodTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\unitCodeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\unitTextTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class TypeAndQuantityNode extends StructuredValue {
+	use amountOfThisGoodTrait, businessFunctionTrait, typeOfGoodTrait,
+		unitCodeTrait, unitTextTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/StructuredValue/WarrantyPromise.php b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/WarrantyPromise.php
new file mode 100644
index 0000000..94f345c
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/WarrantyPromise.php
@@ -0,0 +1,14 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible\StructuredValue;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\WarrantyScope;
+use JVBase\managers\SEO\render\Traits\_Properties\durationOfWarrantyTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\warrantyScopeTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class WarrantyPromise extends StructuredValue {
+	use durationOfWarrantyTrait, warrantyScopeTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/StructuredValue/_setup.php b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/_setup.php
new file mode 100644
index 0000000..0c50fcd
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/StructuredValue/_setup.php
@@ -0,0 +1,17 @@
+<?php
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/StructuredValue/StructuredValue.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/StructuredValue/ContactPoint.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/StructuredValue/GeoCoordinates.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/StructuredValue/GeoShape.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/StructuredValue/InteractionCounter.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/StructuredValue/MonetaryAmount.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/StructuredValue/NutritionalInformation.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/StructuredValue/OpeningHoursSpecification.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/StructuredValue/PriceSpecification.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/StructuredValue/PropertyValue.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/StructuredValue/QuantitativeValue.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/StructuredValue/ServicePeriod.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/StructuredValue/TypeAndQuantityNode.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/StructuredValue/WarrantyPromise.php');
+
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/StructuredValue/LocationFeatureSpecification.php');
diff --git a/inc/managers/SEO/render/Thing/Intangible/VirtualLocation.php b/inc/managers/SEO/render/Thing/Intangible/VirtualLocation.php
new file mode 100644
index 0000000..9da1c7f
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/VirtualLocation.php
@@ -0,0 +1,13 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Intangible;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class VirtualLocation extends Thing
+{
+	//No additional properties. Recommended:
+}
diff --git a/inc/managers/SEO/render/Thing/Intangible/_setup.php b/inc/managers/SEO/render/Thing/Intangible/_setup.php
new file mode 100644
index 0000000..3089337
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Intangible/_setup.php
@@ -0,0 +1,33 @@
+<?php
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Brand/_setup.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/ContactPoint/_setup.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Enumeration/_setup.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/ItemList/_setup.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Quantity/_setup.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Rating/_setup.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Reservation/_setup.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/StructuredValue/_setup.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Audience.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/DefinedTerm.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/CategoryCode.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Demand.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/EntryPoint.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Grant.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/HowToItem.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/HowToSupply.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/HowToTool.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Language.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/ListItem.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/MenuItem.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/MerchantReturnPolicy.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Occupation.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/OccupationalExperienceRequirements.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Offer.php');
+
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/AggregateOffer.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/OfferCatalog.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/PaymentMethod.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Schedule.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/Service.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/ServiceChannel.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/VirtualLocation.php');
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/AnimalShelter.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/AnimalShelter.php
new file mode 100644
index 0000000..70a5325
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/AnimalShelter.php
@@ -0,0 +1,16 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait;
+use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class AnimalShelter extends LocalBusiness {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/AutomotiveBusiness.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/AutomotiveBusiness.php
new file mode 100644
index 0000000..b996fd6
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/AutomotiveBusiness.php
@@ -0,0 +1,16 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait;
+use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class AutomotiveBusiness extends LocalBusiness {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/ChildCare.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/ChildCare.php
new file mode 100644
index 0000000..72af078
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/ChildCare.php
@@ -0,0 +1,16 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait;
+use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class ChildCare extends LocalBusiness {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/Dentist.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/Dentist.php
new file mode 100644
index 0000000..d7a1820
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/Dentist.php
@@ -0,0 +1,16 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait;
+use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class Dentist extends LocalBusiness {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/DryCleaningOrLaundry.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/DryCleaningOrLaundry.php
new file mode 100644
index 0000000..99ccd6b
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/DryCleaningOrLaundry.php
@@ -0,0 +1,16 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait;
+use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class DryCleaningOrLaundry extends LocalBusiness {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/EmergencyService.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/EmergencyService.php
new file mode 100644
index 0000000..02f7bda
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/EmergencyService.php
@@ -0,0 +1,16 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait;
+use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class EmergencyService extends LocalBusiness {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/EntertainmentBusiness.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/EntertainmentBusiness.php
new file mode 100644
index 0000000..e26da73
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/EntertainmentBusiness.php
@@ -0,0 +1,16 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait;
+use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class EntertainmentBusiness extends LocalBusiness {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FinancialService.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FinancialService.php
new file mode 100644
index 0000000..9a5c4fe
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FinancialService.php
@@ -0,0 +1,16 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait;
+use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class FinancialService extends LocalBusiness {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/Bakery.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/Bakery.php
new file mode 100644
index 0000000..e08413e
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/Bakery.php
@@ -0,0 +1,11 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment;
+
+use JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\LocalBusiness;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class Bakery extends FoodEstablishment {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/BarOrPub.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/BarOrPub.php
new file mode 100644
index 0000000..0a98393
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/BarOrPub.php
@@ -0,0 +1,11 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment;
+
+use JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\LocalBusiness;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class BarOrPub extends FoodEstablishment {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/Brewery.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/Brewery.php
new file mode 100644
index 0000000..5cd9760
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/Brewery.php
@@ -0,0 +1,11 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment;
+
+use JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\LocalBusiness;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class Brewery extends FoodEstablishment {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/CafeOrCoffeeShop.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/CafeOrCoffeeShop.php
new file mode 100644
index 0000000..8ff345f
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/CafeOrCoffeeShop.php
@@ -0,0 +1,11 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment;
+
+use JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\LocalBusiness;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class CafeOrCoffeeShop extends FoodEstablishment {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/Distillery.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/Distillery.php
new file mode 100644
index 0000000..0fd8458
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/Distillery.php
@@ -0,0 +1,11 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment;
+
+use JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\LocalBusiness;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class Distillery extends FoodEstablishment {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/FastFoodRestaurant.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/FastFoodRestaurant.php
new file mode 100644
index 0000000..b57c9da
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/FastFoodRestaurant.php
@@ -0,0 +1,11 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment;
+
+use JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\LocalBusiness;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class FastFoodRestaurant extends FoodEstablishment {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/FoodEstablishment.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/FoodEstablishment.php
new file mode 100644
index 0000000..09eb928
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/FoodEstablishment.php
@@ -0,0 +1,16 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment;
+
+use JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\LocalBusiness;
+use JVBase\managers\SEO\render\Traits\_Properties\acceptsReservationsTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\hasMenuTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\servesCuisineTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\starRatingTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class FoodEstablishment extends LocalBusiness {
+	use acceptsReservationsTrait, hasMenuTrait, servesCuisineTrait, starRatingTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/IceCreamShop.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/IceCreamShop.php
new file mode 100644
index 0000000..d4a0e9a
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/IceCreamShop.php
@@ -0,0 +1,11 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment;
+
+use JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\LocalBusiness;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class IceCreamShop extends FoodEstablishment {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/Restaurant.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/Restaurant.php
new file mode 100644
index 0000000..33400dd
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/Restaurant.php
@@ -0,0 +1,11 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment;
+
+use JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\LocalBusiness;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class Restaurant extends FoodEstablishment {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/Winery.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/Winery.php
new file mode 100644
index 0000000..eca4c3d
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/Winery.php
@@ -0,0 +1,11 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment;
+
+use JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\LocalBusiness;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class Winery extends FoodEstablishment {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/_setup.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/_setup.php
new file mode 100644
index 0000000..19615a3
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/_setup.php
@@ -0,0 +1,11 @@
+<?php
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/FoodEstablishment.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/Bakery.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/BarOrPub.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/Brewery.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/CafeOrCoffeeShop.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/Distillery.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/FastFoodRestaurant.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/IceCreamShop.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/Restaurant.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/Winery.php');
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/GovernmentOffice.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/GovernmentOffice.php
new file mode 100644
index 0000000..7d30d1e
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/GovernmentOffice.php
@@ -0,0 +1,16 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait;
+use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class GovernmentOffice extends LocalBusiness {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/HealthAndBeautyBusiness.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/HealthAndBeautyBusiness.php
new file mode 100644
index 0000000..caee134
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/HealthAndBeautyBusiness.php
@@ -0,0 +1,16 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait;
+use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class HealthAndBeautyBusiness extends LocalBusiness {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/HomeAndConstructionBusiness.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/HomeAndConstructionBusiness.php
new file mode 100644
index 0000000..f833889
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/HomeAndConstructionBusiness.php
@@ -0,0 +1,16 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait;
+use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class HomeAndConstructionBusiness extends LocalBusiness {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/InternetCafe.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/InternetCafe.php
new file mode 100644
index 0000000..be8fdfd
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/InternetCafe.php
@@ -0,0 +1,16 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait;
+use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class InternetCafe extends LocalBusiness {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/LegalService.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/LegalService.php
new file mode 100644
index 0000000..1005d25
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/LegalService.php
@@ -0,0 +1,16 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait;
+use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class LegalService extends LocalBusiness {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/Library.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/Library.php
new file mode 100644
index 0000000..16f53af
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/Library.php
@@ -0,0 +1,16 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait;
+use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class Library extends LocalBusiness {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/LocalBusiness.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/LocalBusiness.php
new file mode 100644
index 0000000..08a71f7
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/LocalBusiness.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait;
+use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema;
+use JVBase\managers\SEO\render\Traits\Place\PlaceSchema;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class LocalBusiness extends Organization {
+	use PlaceSchema,
+		currenciesAcceptedTrait, openingHoursTrait,
+		paymentAcceptedTrait, priceRangeTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/LodgingBusiness.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/LodgingBusiness.php
new file mode 100644
index 0000000..54eafb2
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/LodgingBusiness.php
@@ -0,0 +1,16 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait;
+use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class LodgingBusiness extends LocalBusiness {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/MedicalBusiness/MedicalBusiness.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/MedicalBusiness/MedicalBusiness.php
new file mode 100644
index 0000000..4bed11a
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/MedicalBusiness/MedicalBusiness.php
@@ -0,0 +1,11 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\MedicalBusiness;
+
+use JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\LocalBusiness;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class MedicalBusiness extends LocalBusiness {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/MedicalBusiness/_setup.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/MedicalBusiness/_setup.php
new file mode 100644
index 0000000..32c639d
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/MedicalBusiness/_setup.php
@@ -0,0 +1,2 @@
+<?php
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/MedicalBusiness/MedicalBusiness.php');
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/ProfessionalService.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/ProfessionalService.php
new file mode 100644
index 0000000..b37835e
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/ProfessionalService.php
@@ -0,0 +1,16 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait;
+use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class ProfessionalService extends LocalBusiness {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/RadioStation.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/RadioStation.php
new file mode 100644
index 0000000..2809665
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/RadioStation.php
@@ -0,0 +1,16 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait;
+use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class RadioStation extends LocalBusiness {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/RealEstateAgent.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/RealEstateAgent.php
new file mode 100644
index 0000000..969f793
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/RealEstateAgent.php
@@ -0,0 +1,16 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait;
+use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class RealEstateAgent extends LocalBusiness {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/RecyclingCenter.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/RecyclingCenter.php
new file mode 100644
index 0000000..e5783c7
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/RecyclingCenter.php
@@ -0,0 +1,16 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait;
+use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class RecyclingCenter extends LocalBusiness {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/SelfStorage.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/SelfStorage.php
new file mode 100644
index 0000000..9f9a91f
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/SelfStorage.php
@@ -0,0 +1,16 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait;
+use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class SelfStorage extends LocalBusiness {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/ShoppingCenter.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/ShoppingCenter.php
new file mode 100644
index 0000000..da3407d
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/ShoppingCenter.php
@@ -0,0 +1,16 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait;
+use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class ShoppingCenter extends LocalBusiness {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/SportsActivityLocation.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/SportsActivityLocation.php
new file mode 100644
index 0000000..2fc6e9b
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/SportsActivityLocation.php
@@ -0,0 +1,16 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait;
+use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class SportsActivityLocation extends LocalBusiness {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/Store.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/Store.php
new file mode 100644
index 0000000..6cae85d
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/Store.php
@@ -0,0 +1,16 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait;
+use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class Store extends LocalBusiness {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/TelevisionStation.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/TelevisionStation.php
new file mode 100644
index 0000000..a0ec334
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/TelevisionStation.php
@@ -0,0 +1,16 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization\LocalBusiness;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Properties\currenciesAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\openingHoursTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\paymentAcceptedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\priceRangeTrait;
+use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+//TODO Check
+class TelevisionStation extends LocalBusiness {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/LocalBusiness/_setup.php b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/_setup.php
new file mode 100644
index 0000000..3428001
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/LocalBusiness/_setup.php
@@ -0,0 +1,28 @@
+<?php
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/LocalBusiness.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FoodEstablishment/_setup.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/MedicalBusiness/_setup.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/AnimalShelter.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/AutomotiveBusiness.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/ChildCare.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/Dentist.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/DryCleaningOrLaundry.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/EmergencyService.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/EntertainmentBusiness.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/FinancialService.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/GovernmentOffice.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/HealthAndBeautyBusiness.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/HomeAndConstructionBusiness.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/InternetCafe.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/LegalService.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/Library.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/LodgingBusiness.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/ProfessionalService.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/RadioStation.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/RealEstateAgent.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/RecyclingCenter.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/SelfStorage.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/ShoppingCenter.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/SportsActivityLocation.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/Store.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/TelevisionStation.php');
diff --git a/inc/managers/SEO/render/Thing/Organization/OnlineBusiness.php b/inc/managers/SEO/render/Thing/Organization/OnlineBusiness.php
new file mode 100644
index 0000000..db62b77
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/OnlineBusiness.php
@@ -0,0 +1,9 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class OnlineBusiness extends Organization {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/Organization.php b/inc/managers/SEO/render/Thing/Organization/Organization.php
new file mode 100644
index 0000000..f3914c6
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/Organization.php
@@ -0,0 +1,13 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\Organization\OrganizationSchema;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Organization extends Thing {
+	use OrganizationSchema;
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/PerformingGroup.php b/inc/managers/SEO/render/Thing/Organization/PerformingGroup.php
new file mode 100644
index 0000000..8f959cb
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/PerformingGroup.php
@@ -0,0 +1,9 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Organization;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class PerformingGroup extends Organization {
+}
diff --git a/inc/managers/SEO/render/Thing/Organization/_setup.php b/inc/managers/SEO/render/Thing/Organization/_setup.php
new file mode 100644
index 0000000..16b7167
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Organization/_setup.php
@@ -0,0 +1,6 @@
+<?php
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/Organization.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/LocalBusiness/_setup.php');
+
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/OnlineBusiness.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/PerformingGroup.php');
diff --git a/inc/managers/SEO/render/Thing/Person/Person.php b/inc/managers/SEO/render/Thing/Person/Person.php
new file mode 100644
index 0000000..cba8688
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Person/Person.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace JVBase\managers\SEO\render\Thing\Person;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Properties\additionalNameTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\addressTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\affiliationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\awardTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\birthDateTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\birthPlaceTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\brandTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\childrenTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\colleagueTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\contactPointTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\deathDateTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\dunsTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\emailTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\familyNameTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\faxNumberTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\followsTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\funderTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\givenNameTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\globalLocationNumberTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\hasCertificationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\hasCredentialTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\hasOccupationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\hasOfferCatalogTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\hasPOSTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\heightTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\homeLocationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\honorificPrefixTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\honorificSuffixTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\interactionStatisticTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\jobTitleTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\knowsAboutTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\knowsLanguageTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\lifeEventTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\makesOfferTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\memberOfTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\nationalityTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\ownsTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\parentTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\performerInTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\pronounsTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\relatedToTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\seeksTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\siblingTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\skillsTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\sponsorTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\spouseTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\telephoneTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\weightTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\worksAtTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\worksForTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Person extends Thing
+{
+	use additionalNameTrait, addressTrait, affiliationTrait, awardTrait,
+		birthDateTrait, birthPlaceTrait, brandTrait, childrenTrait,
+		colleagueTrait, contactPointTrait, deathDateTrait, deathDateTrait,
+		dunsTrait, emailTrait, familyNameTrait, faxNumberTrait, followsTrait,
+		funderTrait, givenNameTrait, globalLocationNumberTrait, hasCertificationTrait,
+		hasCredentialTrait, hasOccupationTrait, hasOfferCatalogTrait, hasPOSTrait,
+		heightTrait, homeLocationTrait, honorificPrefixTrait, honorificSuffixTrait,
+		interactionStatisticTrait, jobTitleTrait, knowsAboutTrait, knowsLanguageTrait,
+		lifeEventTrait, makesOfferTrait, memberOfTrait, nationalityTrait, ownsTrait,
+		parentTrait, performerInTrait, pronounsTrait, relatedToTrait, seeksTrait, siblingTrait,
+		skillsTrait, sponsorTrait, spouseTrait, telephoneTrait, weightTrait,
+		worksAtTrait, worksForTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Person/_setup.php b/inc/managers/SEO/render/Thing/Person/_setup.php
new file mode 100644
index 0000000..b511fff
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Person/_setup.php
@@ -0,0 +1,2 @@
+<?php
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Person/Person.php');
diff --git a/inc/managers/SEO/render/Thing/Place/AdministrativeArea/AdministrativeArea.php b/inc/managers/SEO/render/Thing/Place/AdministrativeArea/AdministrativeArea.php
new file mode 100644
index 0000000..0f2e992
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Place/AdministrativeArea/AdministrativeArea.php
@@ -0,0 +1,17 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Place\AdministrativeArea;
+
+use JVBase\managers\SEO\render\Thing\Intangible\ContactPoint\PostalAddress;
+use JVBase\managers\SEO\render\Thing\Intangible\Rating\AggregateRating;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\LocationFeatureSpecification;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\PropertyValue;
+use JVBase\managers\SEO\render\Thing\Thing;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class AdministrativeArea extends Thing
+{
+}
diff --git a/inc/managers/SEO/render/Thing/Place/AdministrativeArea/Country.php b/inc/managers/SEO/render/Thing/Place/AdministrativeArea/Country.php
new file mode 100644
index 0000000..3bd6446
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Place/AdministrativeArea/Country.php
@@ -0,0 +1,12 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Place\AdministrativeArea;
+
+use JVBase\managers\SEO\render\Thing\Intangible\ContactPoint\PostalAddress;
+use JVBase\managers\SEO\render\Thing\Thing;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Country extends AdministrativeArea {
+}
diff --git a/inc/managers/SEO/render/Thing/Place/AdministrativeArea/_setup.php b/inc/managers/SEO/render/Thing/Place/AdministrativeArea/_setup.php
new file mode 100644
index 0000000..1faa572
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Place/AdministrativeArea/_setup.php
@@ -0,0 +1,3 @@
+<?php
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Place/AdministrativeArea/AdministrativeArea.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Place/AdministrativeArea/Country.php');
diff --git a/inc/managers/SEO/render/Thing/Place/Place.php b/inc/managers/SEO/render/Thing/Place/Place.php
new file mode 100644
index 0000000..24bdc09
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Place/Place.php
@@ -0,0 +1,12 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Place;
+
+use JVBase\managers\SEO\render\Traits\Place\PlaceSchema;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Place {
+	use PlaceSchema;
+}
diff --git a/inc/managers/SEO/render/Thing/Place/_setup.php b/inc/managers/SEO/render/Thing/Place/_setup.php
new file mode 100644
index 0000000..45b607f
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Place/_setup.php
@@ -0,0 +1,3 @@
+<?php
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Place/AdministrativeArea/_setup.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Place/Place.php');
diff --git a/inc/managers/SEO/render/Thing/Product/Product.php b/inc/managers/SEO/render/Thing/Product/Product.php
new file mode 100644
index 0000000..b1ff016
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Product/Product.php
@@ -0,0 +1,61 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Product;
+
+
+use JVBase\managers\SEO\render\Traits\_Properties\additionalPropertyTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\aggregateRatingTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\audienceTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\awardTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\brandTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\categoryTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\colorTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\countryOfAssemblyTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\countryOfOriginTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\depthTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\displayLocationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\hasAdultConsiderationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\hasCertificationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\hasMeasurementTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\hasMerchantReturnPolicyTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\heightTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\isAccessoryOrSparePartForTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\isConsumableForTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\isFamilyFriendlyTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\isRelatedToTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\isSimilarToTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\isVariantOfTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\itemConditionTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\keywordsTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\logoTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\manufacturerTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\materialTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\negativeNotesTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\offersTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\patternTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\positiveNotesTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\productIDTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\releaseDateTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\reviewTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\sizeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\skuTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\sloganTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\weightTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\widthTrait;
+use JVBase\managers\SEO\render\Traits\ThingSchema;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Product  {
+	use ThingSchema,
+		additionalPropertyTrait, aggregateRatingTrait, audienceTrait,
+		awardTrait, brandTrait, categoryTrait, colorTrait, countryOfAssemblyTrait,
+		countryOfOriginTrait, depthTrait, displayLocationTrait, hasAdultConsiderationTrait,
+		hasCertificationTrait, hasMeasurementTrait, hasMerchantReturnPolicyTrait,
+		heightTrait, isAccessoryOrSparePartForTrait, isConsumableForTrait,
+		isFamilyFriendlyTrait, isRelatedToTrait, isSimilarToTrait, isVariantOfTrait,
+		itemConditionTrait, keywordsTrait, logoTrait, manufacturerTrait, materialTrait, negativeNotesTrait,
+		offersTrait, patternTrait, positiveNotesTrait, productIDTrait, releaseDateTrait,
+		reviewTrait, sizeTrait, skuTrait, sloganTrait, weightTrait, widthTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Product/ProductGroup.php b/inc/managers/SEO/render/Thing/Product/ProductGroup.php
new file mode 100644
index 0000000..57879de
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Product/ProductGroup.php
@@ -0,0 +1,15 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Product;
+
+use JVBase\managers\SEO\render\Traits\_Properties\hasVariantTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\productGroupIDTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\variesByTrait;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class ProductGroup extends Product {
+	use hasVariantTrait, productGroupIDTrait, variesByTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Product/ProductModel.php b/inc/managers/SEO/render/Thing/Product/ProductModel.php
new file mode 100644
index 0000000..7fbaf48
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Product/ProductModel.php
@@ -0,0 +1,14 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Product;
+
+use JVBase\managers\SEO\render\Traits\_Properties\isVariantOfTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\predecessorOfTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\successorOfTrait;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class ProductModel extends Product {
+	use isVariantOfTrait, predecessorOfTrait, successorOfTrait;
+}
diff --git a/inc/managers/SEO/render/Thing/Product/_setup.php b/inc/managers/SEO/render/Thing/Product/_setup.php
new file mode 100644
index 0000000..76b6efd
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Product/_setup.php
@@ -0,0 +1,4 @@
+<?php
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Product/Product.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Product/ProductGroup.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Product/ProductModel.php');
diff --git a/inc/managers/SEO/render/Thing/Properties/_setup.php b/inc/managers/SEO/render/Thing/Properties/_setup.php
new file mode 100644
index 0000000..dc01fa7
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Properties/_setup.php
@@ -0,0 +1,3 @@
+<?php
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Properties/about.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Properties/alternativeHeadline.php');
diff --git a/inc/managers/SEO/render/Thing/Properties/about.php b/inc/managers/SEO/render/Thing/Properties/about.php
new file mode 100644
index 0000000..e428a7a
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Properties/about.php
@@ -0,0 +1,17 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class about {
+	/**
+	 * @var string Defined Schema type
+	 */
+	protected string $type;
+	/**
+	 * @var string The name of the thing
+	 */
+	protected string $name;
+}
diff --git a/inc/managers/SEO/render/Thing/Properties/alternativeHeadline.php b/inc/managers/SEO/render/Thing/Properties/alternativeHeadline.php
new file mode 100644
index 0000000..c220d69
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Properties/alternativeHeadline.php
@@ -0,0 +1,13 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing\Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+final class alternativeHeadline {
+	/**
+	 * @var string A secondary title of the Creative Work
+	 */
+	protected string $headline;
+}
diff --git a/inc/managers/SEO/render/Thing/Thing.php b/inc/managers/SEO/render/Thing/Thing.php
new file mode 100644
index 0000000..447caed
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/Thing.php
@@ -0,0 +1,12 @@
+<?php
+namespace JVBase\managers\SEO\render\Thing;
+
+use JVBase\managers\SEO\render\Traits\ThingSchema;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Thing {
+	use ThingSchema;
+}
diff --git a/inc/managers/SEO/render/Thing/_setup.php b/inc/managers/SEO/render/Thing/_setup.php
new file mode 100644
index 0000000..60065e3
--- /dev/null
+++ b/inc/managers/SEO/render/Thing/_setup.php
@@ -0,0 +1,11 @@
+<?php
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Action.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Thing.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/CreativeWork/_setup.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Event/_setup.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Intangible/_setup.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Organization/_setup.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Person/_setup.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Place/_setup.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Product/_setup.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/Properties/_setup.php');
diff --git a/inc/managers/SEO/render/Traits/Organization/OrganizationSchema.php b/inc/managers/SEO/render/Traits/Organization/OrganizationSchema.php
new file mode 100644
index 0000000..ce13ac4
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/Organization/OrganizationSchema.php
@@ -0,0 +1,79 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\Organization;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+use JVBase\managers\SEO\render\Traits\_Properties\addressTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\aggregateRatingTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\areaServedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\awardTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\brandTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\companyRegistrationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\contactPointTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\departmentTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\dissolutionDateTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\dunsTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\emailTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\employeeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\ethicsPolicyTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\eventTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\faxNumberTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\founderTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\foundingDateTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\foundingLocationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\funderTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\fundingTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\globalLocationNumberTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\hasCertificationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\hasCredentialTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\hasMerchantReturnPolicyTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\hasOfferCatalogTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\hasPOSTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\interactionStatisticTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\keywordsTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\knowsAboutTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\knowsLanguageTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\legalAddressTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\legalNameTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\legalRepresentativeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\locationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\logoTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\makesOfferTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\memberOfTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\memberTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\naicsTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\numberOfEmployeesTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\ownershipFundInfoTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\ownsTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\parentOrganizationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\paymentMethodTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\reviewTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\seeksTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\skillsTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\sloganTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\sponsorTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\subOrganizationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\taxIDTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\telephoneTrait;
+use JVBase\managers\SEO\render\Traits\ThingSchema;
+
+trait OrganizationSchema {
+	use ThingSchema,
+		paymentMethodTrait, addressTrait, aggregateRatingTrait,
+		areaServedTrait, awardTrait, brandTrait, companyRegistrationTrait,
+		contactPointTrait, departmentTrait, dissolutionDateTrait,
+		dunsTrait, emailTrait, employeeTrait, ethicsPolicyTrait,
+		eventTrait, faxNumberTrait, founderTrait, foundingDateTrait,
+		foundingLocationTrait, funderTrait, fundingTrait, globalLocationNumberTrait,
+		hasCertificationTrait, hasCredentialTrait, hasMerchantReturnPolicyTrait,
+		hasOfferCatalogTrait, hasPOSTrait, interactionStatisticTrait,
+		keywordsTrait, knowsAboutTrait, knowsLanguageTrait, legalAddressTrait,
+		legalNameTrait, legalRepresentativeTrait, locationTrait, logoTrait,
+		makesOfferTrait, memberTrait, memberOfTrait, naicsTrait, numberOfEmployeesTrait,
+		ownershipFundInfoTrait, ownsTrait, parentOrganizationTrait,
+		reviewTrait, seeksTrait,skillsTrait, sloganTrait, sponsorTrait,
+		subOrganizationTrait, taxIDTrait, telephoneTrait;
+
+}
diff --git a/inc/managers/SEO/render/Traits/Organization/_setup.php b/inc/managers/SEO/render/Traits/Organization/_setup.php
new file mode 100644
index 0000000..6bc27f5
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/Organization/_setup.php
@@ -0,0 +1,2 @@
+<?php
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/Organization/OrganizationSchema.php');
diff --git a/inc/managers/SEO/render/Traits/Place/PlaceSchema.php b/inc/managers/SEO/render/Traits/Place/PlaceSchema.php
new file mode 100644
index 0000000..30efac2
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/Place/PlaceSchema.php
@@ -0,0 +1,45 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\Place;
+
+use JVBase\managers\SEO\render\Traits\_Properties\additionalPropertyTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\aggregateRatingTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\amenityFeatureTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\branchCodeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\containedInPlaceTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\eventTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\faxNumberTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\geoTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\globalLocationNumberTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\hasCertificationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\hasDriveThroughServiceTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\hasMapTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\isAccessibleForFreeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\keywordsTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\latitudeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\logoTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\longitudeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\maximumAttendeeCapacityTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\openingHoursSpecificationTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\photoTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\postalAddressTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\publicAccessTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\reviewTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\sloganTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\smokingAllowedTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\specialOpeningHoursTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\telephoneTrait;
+use JVBase\managers\SEO\render\Traits\ThingSchema;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait PlaceSchema {
+	use ThingSchema,
+		additionalPropertyTrait, postalAddressTrait, aggregateRatingTrait,
+		amenityFeatureTrait, branchCodeTrait, containedInPlaceTrait, eventTrait,
+		faxNumberTrait, geoTrait, globalLocationNumberTrait, hasCertificationTrait,
+		hasDriveThroughServiceTrait, hasMapTrait, isAccessibleForFreeTrait, keywordsTrait,
+		latitudeTrait, longitudeTrait, logoTrait, maximumAttendeeCapacityTrait, openingHoursSpecificationTrait,
+		photoTrait, publicAccessTrait, reviewTrait, sloganTrait, smokingAllowedTrait,
+		specialOpeningHoursTrait, telephoneTrait;
+}
diff --git a/inc/managers/SEO/render/Traits/Place/_setup.php b/inc/managers/SEO/render/Traits/Place/_setup.php
new file mode 100644
index 0000000..814c300
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/Place/_setup.php
@@ -0,0 +1,2 @@
+<?php
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/Place/PlaceSchema.php');
diff --git a/inc/managers/SEO/render/Traits/ThingSchema.php b/inc/managers/SEO/render/Traits/ThingSchema.php
new file mode 100644
index 0000000..e77d3e1
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/ThingSchema.php
@@ -0,0 +1,121 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+use JVBase\managers\SEO\render\DataType\Date;
+use JVBase\managers\SEO\render\DataType\DateTime;
+use JVBase\managers\SEO\render\DataType\Time;
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Properties\additionalTypeTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\alternateNameTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\descriptionTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\disambiguatingDescriptionTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\imageTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\mainEntityOfPageTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\nameTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\ownerTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\potentialActionTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\sameAsTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\subjectOfTrait;
+use JVBase\managers\SEO\render\Traits\_Properties\urlTrait;
+
+trait ThingSchema {
+	use additionalTypeTrait, alternateNameTrait, descriptionTrait,
+		disambiguatingDescriptionTrait, imageTrait, mainEntityOfPageTrait,
+		nameTrait, ownerTrait, potentialActionTrait, sameAsTrait, subjectOfTrait, urlTrait;
+
+	protected string $id;
+
+	protected array $ignore = [
+		'mappedMethods',
+		'ignore'
+	];
+	public function outputSchema():array
+	{
+		global $wp;
+		$current = home_url( add_query_arg( $_GET, $wp->request ) );
+		$id = (isset($this->id)) ? $this->id : $current.'/#'.strtolower($this->getTypeName());
+		$elements = array_map(
+			function ($value) {
+
+				if (is_a($value, Thing::class)) {
+					return $value->outputSchema();
+				} elseif (is_array($value)) {
+					$temp = [];
+					foreach ($value as $v) {
+						if (is_a($v, Thing::class)) {
+							$temp[] = $v->outputSchema();
+						} else {
+							$temp[] = $v;
+						}
+					}
+					return $temp;
+				}elseif (is_a($value, DateTime::class)) {
+					$value = $value->getDateTime();
+				}else if (is_a($value, Date::class)) {
+					$value = $value->getDate();
+				} else if (is_a($value, Time::class)) {
+					$value = $value->getTime();
+				}else if (!is_string($value)) {
+
+					error_log('Normal value? '.print_r($value, true));
+				}
+
+				return $value;
+			},
+			array_filter(get_object_vars($this), function ($property) {
+				return !in_array($property, $this->ignore);
+			}, ARRAY_FILTER_USE_KEY)
+		);
+
+		return  array_merge([
+			'@type'	=> $this->getTypeName(),
+			'@id'	=> $id,
+		], array_filter($elements));
+	}
+
+	public function getTypeName():string
+	{
+		return (new \ReflectionClass($this))->getShortName();
+	}
+
+	public function getProperties():array
+	{
+		$properties = get_class_vars(self::class);
+		$remove = [
+			'mappedMethods'
+		];
+		return array_filter($properties, function($property) use ($remove) {
+			return !in_array($property, $remove);
+		}, ARRAY_FILTER_USE_KEY);
+	}
+
+
+	public static function fromArray(array $config):self
+	{
+		$instance = new self();
+		foreach ($config as $key => $value) {
+			if (!property_exists($instance, $key)){
+				error_log('[SEO]Could not instantiate '.get_class($instance).' property: '.$key);
+				continue;
+			}
+			$instance->$key = $value;
+		}
+		return $instance;
+	}
+
+	public function getId():string {
+		return $this->id;
+	}
+	public function setId(string $id):void
+	{
+		if (!filter_var($id, FILTER_VALIDATE_URL)) {
+			error_log('[SEO]Could not set id: '.$id.'. Should be a valid URL');
+			return;
+		}
+		$this->id = $id;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Helpers/_setup.php b/inc/managers/SEO/render/Traits/_Helpers/_setup.php
new file mode 100644
index 0000000..4d0fd8c
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Helpers/_setup.php
@@ -0,0 +1,2 @@
+<?php
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Helpers/arrayHelper.php');
diff --git a/inc/managers/SEO/render/Traits/_Helpers/arrayHelper.php b/inc/managers/SEO/render/Traits/_Helpers/arrayHelper.php
new file mode 100644
index 0000000..0c5a941
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Helpers/arrayHelper.php
@@ -0,0 +1,93 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Helpers;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait arrayHelper {
+	protected function classArray(string $property, array $array, string $className):array
+	{
+		return array_filter($array, function ($item) use ($property, $className) {
+			$test = class_exists($className) && is_a($item, $className);
+			if (!$test) {
+				error_log('[SEO]Item property '.$property.' is not an instance of '.$className);
+			}
+			return $test;
+		});
+	}
+	protected function mixedArray(string $property, array $array, array $allowedTypes):array
+	{
+		return array_filter($array, function($item) use ($property, $allowedTypes) {
+			$test = in_array(gettype($item), $allowedTypes) || $this->testClasses($item, $allowedTypes);
+			if (!$test) {
+				error_log('[SEO]Item property '.$property.' is not an allowed type: '.print_r($item, true));
+			}
+			return $test;
+		});
+	}
+	protected function testClasses(mixed $item, array $allowedTypes):bool
+	{
+		if (gettype($item) !== 'object') return false;
+		foreach ($allowedTypes as $type) {
+			if (!class_exists($type)) continue;
+			$test = is_a($item, $type);
+			if ($test){
+				return true;
+			}
+		}
+		return false;
+	}
+	protected function stringArray(string $property, array $array):array
+	{
+		return array_filter($array, function($item) use($property) {
+			if (!is_string($item)) {
+				error_log('[SEO]Item property '.$property.' is not string: '.print_r($item, true));
+			}
+			return is_string($item);
+		});
+	}
+	protected function urlArray(string $property, array $array):array
+	{
+		return array_filter($array, function($item) use($property) {
+			$test = filter_var($item, FILTER_VALIDATE_URL);
+			if (!$test) {
+				error_log('[SEO]Item property '.$property.' is not URL: '.print_r($item, true));
+			}
+			return $test;
+		});
+	}
+	protected function intArray(string $property, array $array):array
+	{
+		return array_filter($array, function($item) use($property) {
+			if (!is_int($item)) {
+				error_log('[SEO]Item property '.$property.' is not int: '.print_r($item, true));
+			}
+			return is_int($item);
+		});
+	}
+	protected function floatArray(string $property, array $array):array
+	{
+		return array_filter($array, function($item) use($property) {
+			if (!is_float($item)) {
+				error_log('[SEO]Item property '.$property.' is not float: '.print_r($item, true));
+			}
+			return is_float($item);
+		});
+	}
+
+	protected function testAllowed(string $property, array $mappedTypes, array $testTypes):array
+	{
+		return array_map(
+			function($item) use ($mappedTypes) {
+				return $mappedTypes[strtolower($item)];
+			},
+			array_filter($testTypes, function($item) use($property, $mappedTypes) {
+				$test = array_key_exists(strtolower($item), $mappedTypes);
+				if (!$test) {
+					error_log('[SEO]Item property '.$property.' has invalid type: '.$item);
+				}
+				return $test;
+			})
+		);
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/_a.php b/inc/managers/SEO/render/Traits/_Properties/_a.php
new file mode 100644
index 0000000..5772e44
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/_a.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait _a {
+	/**
+	 * @var string A description of the item
+	 */
+	protected string $description;
+
+	public function getDescription():?string
+	{
+		return $this->description??null;
+	}
+	public function setDescription(string $description):void
+	{
+		$this->description = $description;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/_setup.php b/inc/managers/SEO/render/Traits/_Properties/_setup.php
new file mode 100644
index 0000000..7877c1d
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/_setup.php
@@ -0,0 +1,447 @@
+<?php
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/aboutTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/abstractTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/acceptedPaymentMethodTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/acceptsReservationsTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/accountablePersonTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/acquireLicensePageTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/actionPlatformTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/actionProcessTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/actionStatusTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/actorTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/additionalNameTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/additionalPropertyTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/additionalTypeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/addOnTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/addressCountryTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/addressLocalityTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/addressRegionTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/addressTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/advancedBookingRequirementTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/affiliationTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/agentTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/aggregateElementTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/aggregateRatingTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/alternateNameTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/alternativeHeadlineTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/amenityFeatureTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/amountOfThisGoodTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/applicableCountryTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/areaServedTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/associatedMediaTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/attendeeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/audienceTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/audienceTypeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/audioTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/auditDateTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/authorTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/availabilityEndsTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/availabilityStartsTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/availabilityTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/availableAtOrFromTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/availableChannelTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/availableDeliveryMethodTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/availableLanguageTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/awardTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/bestRatingTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/birthDateTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/birthPlaceTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/bitrateTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/bookingTimeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/boxTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/branchCodeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/brandTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/breadcrumbTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/brokerTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/businessDaysTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/businessFunctionTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/byArtistTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/byDayTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/byMonthDayTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/byMonthTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/byMonthWeekTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/caloriesTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/captionTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/carbohydrateContentTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/categoryTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/certificationIdentificationTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/certificationRatingTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/certificationStatusTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/characterTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/checkoutPageURLTemplateTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/childrenTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/cholesterolContentTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/circleTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/citationTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/clipNumberTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/closesTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/codeValueTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/colleagueTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/colorTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/companyRegistrationTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/composerTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/contactOptionTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/contactPointTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/contactTypeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/containedInPlaceTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/contentLocationTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/contentRatingTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/contentReferenceTimeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/contentSizeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/contentTypeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/contentUrlTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/contributorTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/copyrightHolderTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/copyrightNoticeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/copyrightYearTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/countryOfAssemblyTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/countryOfOriginTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/countryTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/creativeWorkStatusTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/creatorTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/creditTextTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/currenciesAcceptedTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/currencyTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/cutoffTimeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/dateCreatedTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/dateModifiedTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/datePublishedTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/dayOfWeekTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/deathDateTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/deathPlaceTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/deliveryLeadTimeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/departmentTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/depthTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/descriptionTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/directorTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/disambiguatingDescriptionTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/discussionUrlTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/displayLocationTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/dissolutionDateTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/doorTimeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/dunsTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/durationOfWarrantyTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/durationTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/editorTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/educationRequirementsTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/elevationTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/eligibleCustomerTypeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/eligibleDurationTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/eligibleRegionTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/eligibleQuantityTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/eligibleTransactionVolumeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/emailTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/embeddedTextCaptionTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/embedURLTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/employeeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/encodingFormatTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/encodingTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/encodingTypeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/endDateTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/endOffsetTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/endTimeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/errorTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/estimateCostTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/ethicsPolicyTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/eventTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/exampleOfWorkTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/exceptDateTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/exifDataTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/experienceRequirementsTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/expiresTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/extendedAddressTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/familyNameTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/fatContentTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/faxNumberTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/fiberContentTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/followsTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/founderTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/foundingDateTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/foundingLocationTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/fundedItemTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/funderTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/fundingTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/genreTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/geographicAreaTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/geoTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/givenNameTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/globalLocationNumberTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/gtinTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/hasAdultConsiderationTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/hasCategoryCodeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/hasCertificationTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/hasCredentialTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/hasDefinedTermTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/hasDriveThroughServiceTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/hasMapTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/hasMeasurementTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/hasMenuItemTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/hasMenuSectionTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/hasMenuTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/hasMerchantReturnPolicyTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/hasOccupationTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/hasOfferCatalogTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/hasPartTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/hasPOSTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/hasVariantTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/headlineTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/heightTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/highPriceTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/homeLocationTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/honorificPrefixTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/honorificSuffixTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/hoursAvailableTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/httpMethodTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/imageTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/includesObjectTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/inCodeSetTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/inDefinedTermSetTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/ineligibleLocationTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/ineligibleRegionTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/inGroupProductWithIDTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/inLanguageTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/inStoreReturnsOfferedTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/instrumentTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/interactionServiceTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/interactionStatisticTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/interactionTypeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/inventoryLevelTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/isAccessibleForFreeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/isAccessoryOrSparePartForTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/isBasedOnTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/isConsumableForTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/isFamilyFriendlyTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/isPartOfTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/isRelatedToTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/isSimilarToTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/issnTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/issuedByTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/isVariantOfTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/itemConditionTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/itemListElementTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/itemListOrderTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/itemOfferedTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/itemReviewedTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/itemTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/jobTitleTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/keywordsTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/knowsAboutTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/knowsLanguageTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/knowsTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/languageTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/lastReviewedTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/latitudeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/legalAddressTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/legalNameTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/legalRepresentativeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/licenseTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/lifeEventTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/lineTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/locationCreatedTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/locationTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/logoTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/longitudeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/lowPriceTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/mainEntityOfPageTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/mainEntityTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/maintainerTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/makesOfferTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/manufacturerTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/materialExtentTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/materialTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/maximumAttendeeCapacityTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/maximumPhysicalAttendeeCapacityTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/maximumVirtualAttendeeCapacityTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/maxPriceTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/maxValueTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/memberOfTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/membershipPointsEarnedTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/memberTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/mentionsTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/menuAddOnTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/merchantReturnDaysTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/merchantReturnLinkTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/minPriceTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/minValueTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/mobileUrlTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/monthsOfExperienceTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/musicByTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/naicsTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/nameTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/nationalityTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/negativeNotesTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/nextItemTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/numberOfEmployeesTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/numberOfItemsTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/nutritionTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/objectTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/occupationalCategoryTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/occupationLocationTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/offerCountTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/offeredByTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/offersTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/openingHoursSpecificationTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/openingHoursTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/opensTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/organizerTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/ownershipFundInfoTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/ownerTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/ownsTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/parentOrganizationTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/parentTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/participantTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/partySizeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/patternTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/paymentAcceptedTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/paymentMethodTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/performerInTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/performerTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/performTimeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/photoTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/polygonTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/positionTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/positiveNotesTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/postalAddressTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/postalCodeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/postOfficeBoxNumberTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/potentialActionTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/predecessorOfTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/prepTimeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/previousItemTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/previousStartDateTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/priceCurrencyTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/priceRangeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/priceSpecificationTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/priceTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/priceValidUntilTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/primaryImageOfPageTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/processingTimeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/producerTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/productGroupIDTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/productIDTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/productSupportedTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/pronounsTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/propertyIDTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/proteinContentTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/providerMobilityTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/providerTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/providesServiceTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/publicAccessTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/publisherImprintTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/publisherTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/publishingPrinciplesTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/qualificationsTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/ratingCountTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/ratingValueTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/recordedAtTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/recordedInTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/refundTypeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/regionsAllowedTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/relatedLinkTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/relatedToTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/releaseDateTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/remainingAttendeeCapacityTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/repeatCountTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/repeatFrequencyTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/representativeOfPageTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/requiredQuantityTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/reservationForTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/reservationIdTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/responsibilitiesTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/resultTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/reviewBodyTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/reviewCountTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/reviewedByTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/reviewRatingTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/reviewTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/sameAsTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/saturatedFatContentTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/scheduleTimezoneTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/sdDatePublishedTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/sdLicenseTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/sdPublisherTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/seeksTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/sellerTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/serialNumberTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/servesCuisineTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/serviceLocationTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/serviceOutputTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/servicePhoneTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/servicePostalAddressTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/serviceSmsNumberTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/serviceTypeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/serviceUrlTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/servingSizeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/siblingTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/significantLinkTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/sizeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/skillsTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/skuTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/sloganTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/smokingAllowedTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/sodiumContentTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/sourceOrganizationTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/spatialCoverageTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/spatialTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/specialOpeningHoursTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/sponsorTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/spouseTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/starRatingTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/startDateTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/startOffsetTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/startTimeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/streetAddressTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/subEventTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/subjectOfTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/subOrganizationTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/successorOfTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/sugarContentTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/suitableForDietTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/superEventTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/targetTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/taxIDTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/teachesTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/telephoneTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/temporalCoverageTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/termCodeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/termsOfServiceTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/textTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/thumbnailTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/thumbnailUrlTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/timeRequiredTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/totalPriceTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/totalTimeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/transcriptTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/transFatContentTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/translationOfWorkTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/translatorTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/typeOfGoodTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/typicalAgeRangeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/unitCodeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/unitTextTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/unsaturatedFatContentTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/urlTemplateTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/urlTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/userInteractionCountTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/validFromTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/validInTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/validThroughTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/valueReferenceTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/valueTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/variesByTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/versionTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/videoFrameSizeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/videoQualityTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/videoTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/warrantyScopeTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/warrantyTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/weightTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/widthTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/wordCountTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/workExampleTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/workFeaturedTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/workPerformedTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/worksAtTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/worksForTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/workTranslationTrait.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/worstRatingTrait.php');
diff --git a/inc/managers/SEO/render/Traits/_Properties/aboutTrait.php b/inc/managers/SEO/render/Traits/_Properties/aboutTrait.php
new file mode 100644
index 0000000..633666a
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/aboutTrait.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait aboutTrait {
+	/**
+	 * @var Thing The subject matter of an object.
+	 * Inverse property: subjectOf
+	 */
+	protected Thing $about;
+
+	public function getAbout():?Thing
+	{
+		return $this->about??null;
+	}
+	public function setAbout(Thing $about):void
+	{
+		$this->about = $about;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/abstractTrait.php b/inc/managers/SEO/render/Traits/_Properties/abstractTrait.php
new file mode 100644
index 0000000..f707334
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/abstractTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait abstractTrait {
+	/**
+	 * @var string An abstract is a short abstract that summarizes a CreativeWork.
+	 */
+	protected string $abstract;
+
+	public function getAbstract():?string
+	{
+		return $this->abstract??null;
+	}
+	public function setAbstract(string $abstract):void
+	{
+		$this->abstract = $abstract;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/acceptedPaymentMethodTrait.php b/inc/managers/SEO/render/Traits/_Properties/acceptedPaymentMethodTrait.php
new file mode 100644
index 0000000..50c844d
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/acceptedPaymentMethodTrait.php
@@ -0,0 +1,31 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\PaymentMethod;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait acceptedPaymentMethodTrait {
+	use arrayHelper;
+	/**
+	 * @var PaymentMethod|string|array The payment method(s) that are accepted in general by an organization, or for some specific demand or offer.
+	 */
+	protected PaymentMethod|string|array $acceptedPaymentMethods;
+
+	public function getAcceptedPaymentMethods():PaymentMethod|string|array|null
+	{
+		return $this->acceptedPaymentMethods??null;
+	}
+	public function setAcceptedPaymentMethods(PaymentMethod|string|array $acceptedPaymentMethods):void
+	{
+		if (is_array($acceptedPaymentMethods)) {
+			$acceptedPaymentMethods = $this->mixedArray('acceptedPaymentMethods', $acceptedPaymentMethods, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\Intangible\PaymentMethod'
+			]);
+		}
+		$this->acceptedPaymentMethods = $acceptedPaymentMethods;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/acceptsReservationsTrait.php b/inc/managers/SEO/render/Traits/_Properties/acceptsReservationsTrait.php
new file mode 100644
index 0000000..099b420
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/acceptsReservationsTrait.php
@@ -0,0 +1,27 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait acceptsReservationsTrait {
+	/**
+	 * @var bool|string Indicates whether a FoodEstablishment accepts reservations. Values can be Boolean, an URL at which reservations can be made or (for backwards compatibility) the strings Yes or No.
+	 */
+	protected bool|string $acceptsReservations;
+
+	public function getAcceptsReservations():bool|string|null
+	{
+		return $this->acceptsReservations??null;
+	}
+	public function setAcceptsReservations(bool|string $acceptsReservations):void
+	{
+		if (is_string($acceptsReservations)) {
+			if (!filter_var($acceptsReservations, FILTER_VALIDATE_URL)) {
+				error_log('[SEO] AcceptsReservations must be a URL pointing to where reservations can be made, or a boolean value.');
+				return;
+			}
+		}
+		$this->acceptsReservations = $acceptsReservations;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/accountablePersonTrait.php b/inc/managers/SEO/render/Traits/_Properties/accountablePersonTrait.php
new file mode 100644
index 0000000..fcba7ae
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/accountablePersonTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait accountablePersonTrait {
+	use arrayHelper;
+	/**
+	 * @var Person|array Specifies the Person that is legally accountable for the CreativeWork.
+	 */
+	protected Person|array $accountablePerson;
+
+	public function getAccountablePerson():Person|array|null
+	{
+		return $this->accountablePerson??null;
+	}
+	public function setAccountablePerson(Person|array $accountablePerson):void
+	{
+		if (is_array($accountablePerson)) {
+			$accountablePerson = $this->classArray('accountablePerson', $accountablePerson, 'JVBase\managers\SEO\render\Thing\Person\Person');
+		}
+		$this->accountablePerson = $accountablePerson;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/acquireLicensePageTrait.php b/inc/managers/SEO/render/Traits/_Properties/acquireLicensePageTrait.php
new file mode 100644
index 0000000..1a4c510
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/acquireLicensePageTrait.php
@@ -0,0 +1,29 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait acquireLicensePageTrait {
+	/**
+	 * @var CreativeWork|string Indicates a page documenting how licenses can be purchased or otherwise acquired, for the current item.
+	 */
+	protected CreativeWork|string $acquireLicensePage;
+
+	public function getAcquireLicensePage():CreativeWork|string|null
+	{
+		return $this->acquireLicensePage??null;
+	}
+	public function setAcquireLicensePage(CreativeWork|string $acquireLicensePage):void
+	{
+		if (is_string($acquireLicensePage)) {
+			if (!filter_var($acquireLicensePage, FILTER_VALIDATE_URL)) {
+				error_log('[SEO] acquireLicensePage is meant to be a url');
+				return;
+			}
+		}
+		$this->acquireLicensePage = $acquireLicensePage;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/actionPlatformTrait.php b/inc/managers/SEO/render/Traits/_Properties/actionPlatformTrait.php
new file mode 100644
index 0000000..df2ac48
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/actionPlatformTrait.php
@@ -0,0 +1,31 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\DigitalPlatformEnumeration;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait actionPlatformTrait {
+	use arrayHelper;
+	/**
+	 * @var DigitalPlatformEnumeration|string|array The high level platform(s) where the Action can be performed for the given URL. To specify a specific application or operating system instance, use actionApplication.
+	 */
+	protected DigitalPlatformEnumeration|string|array $actionPlatform;
+
+	public function getActionPlatform():DigitalPlatformEnumeration|string|array|null
+	{
+		return $this->actionPlatform??null;
+	}
+	public function setActionPlatform(DigitalPlatformEnumeration|string|array $actionPlatform):void
+	{
+		if (is_array($actionPlatform)) {
+			$actionPlatform = $this->mixedArray('actionPlatform', $actionPlatform, [
+				'string',
+				'DigitalPlatformEnumeration'
+			]);
+		}
+		$this->actionPlatform = $actionPlatform;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/actionProcessTrait.php b/inc/managers/SEO/render/Traits/_Properties/actionProcessTrait.php
new file mode 100644
index 0000000..d35c54b
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/actionProcessTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\HowTo;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait actionProcessTrait {
+	/**
+	 * @var HowTo ActionProcess of the process by which the action was performed.
+	 */
+	protected HowTo $actionProcess;
+
+	public function getActionProcess():?HowTo
+	{
+		return $this->actionProcess??null;
+	}
+	public function setActionProcess(HowTo $actionProcess):void
+	{
+		$this->actionProcess = $actionProcess;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/actionStatusTrait.php b/inc/managers/SEO/render/Traits/_Properties/actionStatusTrait.php
new file mode 100644
index 0000000..efb2dbb
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/actionStatusTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\ActionStatusType;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait actionStatusTrait {
+	/**
+	 * @var ActionStatusType Indicates the current disposition of the Action.
+	 */
+	protected ActionStatusType $actionStatus;
+
+	public function getActionStatus():?ActionStatusType
+	{
+		return $this->actionStatus??null;
+	}
+	public function setActionStatus(ActionStatusType $actionStatus):void
+	{
+		$this->actionStatus = $actionStatus;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/actorTrait.php b/inc/managers/SEO/render/Traits/_Properties/actorTrait.php
new file mode 100644
index 0000000..85f673b
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/actorTrait.php
@@ -0,0 +1,29 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait actorTrait {
+	use arrayHelper;
+	/**
+	 * @var Person|array An actor (individual or a group), e.g. in TV, radio, movie, video games etc., or in an event. Actors can be associated with individual items or with a series, episode, clip. Supersedes actors.
+	 * Note: Can also be PerformingGroup, not included
+	 */
+	protected Person|array $actor;
+
+	public function getActor():Person|array|null
+	{
+		return $this->actor??null;
+	}
+	public function setActor(Person|array $actor):void
+	{
+		if (is_array($actor)) {
+			$actor = $this->classArray('actor', $actor, 'JVBase\managers\SEO\render\Thing\Person\Person');
+		}
+		$this->actor = $actor;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/addOnTrait.php b/inc/managers/SEO/render/Traits/_Properties/addOnTrait.php
new file mode 100644
index 0000000..e708561
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/addOnTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Offer;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait addOnTrait {
+	use arrayHelper;
+	/**
+	 * @var Offer|array An additional offer that can only be obtained in combination with the first base offer (e.g. supplements and extensions that are available for a surcharge).
+	 */
+	protected Offer|array $addOn;
+
+	public function getAddOn():Offer|array|null
+	{
+		return $this->addOn??null;
+	}
+	public function setAddOn(Offer|array $addOn):void
+	{
+		if (is_array($addOn)) {
+			$addOn = $this->classArray('addOn', $addOn, 'JVBase\managers\SEO\render\Thing\Intangible\Offer');
+		}
+		$this->addOn = $addOn;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/additionalNameTrait.php b/inc/managers/SEO/render/Traits/_Properties/additionalNameTrait.php
new file mode 100644
index 0000000..dbae7f0
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/additionalNameTrait.php
@@ -0,0 +1,27 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait additionalNameTrait {
+	use arrayHelper;
+	/**
+	 * @var string|array An additional name for a Person, can be used for a middle name.
+	 */
+	protected string|array $additionalName;
+
+	public function getAdditionalName():string|array|null
+	{
+		return $this->additionalName??null;
+	}
+	public function setAdditionalName(string|array $additionalName):void
+	{
+		if (is_array($additionalName)) {
+			$additionalName = $this->stringArray('additionalName',$additionalName);
+		}
+		$this->additionalName = $additionalName;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/additionalPropertyTrait.php b/inc/managers/SEO/render/Traits/_Properties/additionalPropertyTrait.php
new file mode 100644
index 0000000..8102e25
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/additionalPropertyTrait.php
@@ -0,0 +1,54 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\PropertyValue;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait additionalPropertyTrait {
+	use arrayHelper;
+	/**
+	 * @var PropertyValue|array A property-value pair representing an additional characteristic of the entity, e.g. a product feature or another characteristic for which there is no matching property in schema.org.
+	 *
+	 * Note: Publishers should be aware that applications designed to use specific schema.org properties (e.g. https://schema.org/width, https://schema.org/color, https://schema.org/gtin13, ...) will typically expect such data to be provided using those properties, rather than using the generic property/value mechanism.
+	 */
+	protected PropertyValue|array $additionalProperty;
+
+	public function getAdditionalProperty():PropertyValue|array|null
+	{
+		return $this->additionalProperty??null;
+	}
+	public function setAdditionalProperty(PropertyValue|array $additionalProperty):void
+	{
+		if (is_array($additionalProperty)) {
+			$additionalProperty = $this->classArray('additionalProperty', $additionalProperty, 'JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\PropertyValue');
+		}
+		$this->additionalProperty = $additionalProperty;
+	}
+
+	public function getAdditionalPropertyFieldConfig():array
+	{
+		return [
+			'type'	=> 'repeater',
+			'label'	=> 'Additional Properties',
+			'hint'	=> 'If you need to define another property that does not exist anywhere on this page, add it here.',
+			'fields'	=> [
+				'name'	=> [
+					'type'	=> 'text',
+					'label'	=> 'Name'
+				],
+				'propertyID'	=> [
+					'type'	=> 'text',
+					'label'	=> 'Property ID',
+					'hint'	=> 'A commonly used identifier for the characteristic represented by the property, e.g. a manufacturer or a standard code for a property. propertyID can be (1) a prefixed string, mainly meant to be used with standards for product properties; (2) a site-specific, non-prefixed string (e.g. the primary key of the property or the vendor-specific ID of the property), or (3) a URL indicating the type of the property, either pointing to an external vocabulary, or a Web resource that describes the property (e.g. a glossary entry). Standards bodies should promote a standard prefix for the identifiers of properties from their standards.'
+				],
+				'value'	=> [
+					'type'	=> 'textarea',
+					'label'	=> 'Value'
+				]
+			]
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/additionalTypeTrait.php b/inc/managers/SEO/render/Traits/_Properties/additionalTypeTrait.php
new file mode 100644
index 0000000..c95e88d
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/additionalTypeTrait.php
@@ -0,0 +1,26 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait additionalTypeTrait {
+	/**
+	 * @var string An additional type for the item, typically used for adding more specific types from external vocabularies in microdata syntax.
+	 * Can be a URL
+	 */
+	protected string $additionalType;
+
+	public function getAdditionalType():?string
+	{
+		return $this->additionalType??null;
+	}
+	public function setAdditionalType(string $additionalType):void
+	{
+		if (filter_var($additionalType, FILTER_VALIDATE_URL)) {
+			$this->additionalType = sanitize_url($additionalType);
+		} else {
+			$this->additionalType = sanitize_text_field($additionalType);
+		}
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/addressCountryTrait.php b/inc/managers/SEO/render/Traits/_Properties/addressCountryTrait.php
new file mode 100644
index 0000000..1767843
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/addressCountryTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Place\AdministrativeArea\Country;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait addressCountryTrait {
+	/**
+	 * @var Country|string The country. Recommended to be in 2-letter ISO 3166-1 alpha-2 format, for example "US". For backward compatibility, a 3-letter ISO 3166-1 alpha-3 country code such as "SGP" or a full country name such as "Singapore" can also be used.
+	 */
+	protected Country|string $addressCountry;
+
+	public function getAddressCountry():Country|string|null
+	{
+		return $this->addressCountry??null;
+	}
+	public function setAddressCountry(Country|string $addressCountry):void
+	{
+		$this->addressCountry = $addressCountry;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/addressLocalityTrait.php b/inc/managers/SEO/render/Traits/_Properties/addressLocalityTrait.php
new file mode 100644
index 0000000..aaf7f32
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/addressLocalityTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait addressLocalityTrait {
+	/**
+	 * @var string The locality in which the street address is, and which is in the region. For example, Mountain View.
+	 */
+	protected string $addressLocality;
+
+	public function getAddressLocality():?string
+	{
+		return $this->addressLocality??null;
+	}
+	public function setAddressLocality(string $addressLocality):void
+	{
+		$this->addressLocality = $addressLocality;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/addressRegionTrait.php b/inc/managers/SEO/render/Traits/_Properties/addressRegionTrait.php
new file mode 100644
index 0000000..c835fbd
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/addressRegionTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Place\AdministrativeArea\AdministrativeArea;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait addressRegionTrait {
+	/**
+	 * @var AdministrativeArea|string The region in which the locality is, and which is in the country. For example, California or another appropriate first-level Administrative division.
+	 */
+	protected AdministrativeArea|string $addressRegion;
+
+	public function getAddressRegion():AdministrativeArea|string|null
+	{
+		return $this->addressRegion??null;
+	}
+	public function setAddressRegion(AdministrativeArea|string $addressRegion):void
+	{
+		$this->addressRegion = $addressRegion;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/addressTrait.php b/inc/managers/SEO/render/Traits/_Properties/addressTrait.php
new file mode 100644
index 0000000..0ea5736
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/addressTrait.php
@@ -0,0 +1,70 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\ContactPoint\PostalAddress;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait addressTrait {
+	/**
+	 * @var PostalAddress|string Physical address of the item.
+	 */
+	protected PostalAddress|string $address;
+
+	public function getAddress():PostalAddress|string|null
+	{
+		return $this->address??null;
+	}
+	public function setAddress(PostalAddress|array|string $address):void
+	{
+		if (is_array($address)){
+			$address = PostalAddress::fromArray($address);
+		}
+		$this->address = $address;
+	}
+	public function getAddressFieldConfig():array
+	{
+		return [
+			'type'	=> 'group',
+			'label'	=> 'Address',
+			'wrap'	=> 'details',
+			'fields'	=> [
+				'streetAddress'	=> [
+					'type' => 'text',
+					'label'=> 'Street Address',
+					'hint'	=> 'The street address. For example, "6551 111 St NW"'
+				],
+				'extendedAddress'	=> [
+					'type' => 'text',
+					'label'=> 'Extended Address',
+					'hint'	=> 'An address extension such as an apartment number, C/O or alternative name.'
+				],
+				'postOfficeBoxNumber'	=> [
+					'type' => 'text',
+					'label' => 'PO Box Number',
+				],
+				'addressLocality'	=> [
+					'type'	=> 'text',
+					'label'	=> 'Address Locality',
+					'hint'	=> 'The locality in which the street address is, and which is in the region. For example, "Park Allen".'
+				],
+				'addressRegion'	=> [
+					'type'	=> 'text',
+					'label'	=> 'Address Region (Province)',
+				],
+				'postalCode'	=> [
+					'type'	=> 'text',
+					'label'	=> 'Postal Code',
+					'hint'	=> 'The postal code. For example, T6H 4R5.'
+				],
+				'addressCountry'	=> [
+					'type'	=> 'text',
+					'label'	=> 'Country',
+					'hint'	=> 'The address country. For example, "CA".',
+					'default' => 'CA'
+				]
+			]
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/advancedBookingRequirementTrait.php b/inc/managers/SEO/render/Traits/_Properties/advancedBookingRequirementTrait.php
new file mode 100644
index 0000000..521dd57
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/advancedBookingRequirementTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait advancedBookingRequirementTrait {
+	/**
+	 * @var QuantitativeValue The amount of time that is required between accepting the offer and the actual usage of the resource or service.
+	 */
+	protected QuantitativeValue $advancedBookingRequirement;
+
+	public function getAdvancedBookingRequirement():?QuantitativeValue
+	{
+		return $this->advancedBookingRequirement??null;
+	}
+	public function setAdvancedBookingRequirement(QuantitativeValue $advancedBookingRequirement):void
+	{
+		$this->advancedBookingRequirement = $advancedBookingRequirement;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/affiliationTrait.php b/inc/managers/SEO/render/Traits/_Properties/affiliationTrait.php
new file mode 100644
index 0000000..a7344e9
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/affiliationTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait affiliationTrait {
+	use arrayHelper;
+	/**
+	 * @var Organization|array An organization that this person is affiliated with. For example, a school/university, a club, or a team.
+	 */
+	protected Organization|array $affiliation;
+
+	public function getAffiliation():Organization|array|null
+	{
+		return $this->affiliation??null;
+	}
+	public function setAffiliation(Organization|array $affiliation):void
+	{
+		if (is_array($affiliation)) {
+			$affiliation =$this->classArray('affiliation', $affiliation, 'JVBase\managers\SEO\render\Thing\Organization\Organization');
+		}
+		$this->affiliation = $affiliation;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/agentTrait.php b/inc/managers/SEO/render/Traits/_Properties/agentTrait.php
new file mode 100644
index 0000000..b4c9cc9
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/agentTrait.php
@@ -0,0 +1,32 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait agentTrait {
+	use arrayHelper;
+	/**
+	 * @var Organization|Person|array The direct performer or driver of the action (animate or inanimate). E.g. John wrote a book.
+	 */
+	protected Organization|Person|array $agent;
+
+	public function getAgent():Organization|Person|array|null
+	{
+		return $this->agent??null;
+	}
+	public function setAgent(Organization|Person|array $agent):void
+	{
+		if (is_array($agent)) {
+			$agent = $this->mixedArray('agent', $agent, [
+				'JVBase\managers\SEO\render\Thing\Organization\Organization',
+				'JVBase\managers\SEO\render\Thing\Person\Person'
+			]);
+		}
+		$this->agent = $agent;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/aggregateElementTrait.php b/inc/managers/SEO/render/Traits/_Properties/aggregateElementTrait.php
new file mode 100644
index 0000000..87386af
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/aggregateElementTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait aggregateElementTrait {
+	/**
+	 * @var Thing Indicates a prototype of the elements in the list that is used to hold aggregate information (ratings, offers, etc.).
+	 */
+	protected Thing $aggregateElement;
+
+	public function getAggregateElement():?Thing
+	{
+		return $this->aggregateElement??null;
+	}
+	public function setAggregateElement(Thing $aggregateElement):void
+	{
+		$this->aggregateElement = $aggregateElement;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/aggregateRatingTrait.php b/inc/managers/SEO/render/Traits/_Properties/aggregateRatingTrait.php
new file mode 100644
index 0000000..a32732f
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/aggregateRatingTrait.php
@@ -0,0 +1,55 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Rating\AggregateRating;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait aggregateRatingTrait {
+	/**
+	 * @var AggregateRating The overall rating, based on a collection of reviews or ratings, of the item.
+	 */
+	protected AggregateRating $aggregateRating;
+
+	public function getAggregateRating():?AggregateRating
+	{
+		return $this->aggregateRating??null;
+	}
+	public function setAggregateRating(array|AggregateRating $aggregateRating):void
+	{
+		if (is_array($aggregateRating)){
+			$aggregateRating =AggregateRating::fromArray($aggregateRating);
+		}
+		$this->aggregateRating = $aggregateRating;
+	}
+
+	public function getAggregateRatingFieldConfig():array
+	{
+		return [
+			'type'	=> 'group',
+			'label'	=> 'Aggregate/Average Rating',
+			'wrap'	=> 'details',
+			'fields'	=> [
+				'ratingCount'	=> [
+					'type'	=> 'number',
+					'label'	=> 'The total number of ratings (without text)',
+				],
+				'reviewCount'	=> [
+					'type'	=> 'number',
+					'label'	=> 'The total number of reviews (with text)'
+				],
+				'bestRating'	=> [
+					'type'	=> 'number',
+					'label'	=> 'The best rating',
+					'default'=> 5,
+				],
+				'worstRating'	=> [
+					'type'	=> 'number',
+					'label'	=> 'The worst rating',
+					'default'=> 1,
+				]
+			]
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/alternateNameTrait.php b/inc/managers/SEO/render/Traits/_Properties/alternateNameTrait.php
new file mode 100644
index 0000000..8eb38e5
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/alternateNameTrait.php
@@ -0,0 +1,30 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait alternateNameTrait {
+	use arrayHelper;
+	/**
+	 * @var string|array An alias for the item, or multiple
+	 */
+	protected string|array $alternateName;
+
+	public function getAlternateName():string|array|null
+	{
+		return $this->alternateName??null;
+	}
+	public function setAlternateName(string|array $alternateName):void
+	{
+		if (is_array($alternateName)) {
+			$alternateName = $this->stringArray('alternateName', $alternateName);
+			if (empty($alternateName)){
+				return;
+			}
+		}
+		$this->alternateName = $alternateName;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/alternativeHeadlineTrait.php b/inc/managers/SEO/render/Traits/_Properties/alternativeHeadlineTrait.php
new file mode 100644
index 0000000..9f7d34f
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/alternativeHeadlineTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait alternativeHeadlineTrait {
+	/**
+	 * @var string A secondary title of the CreativeWork.
+	 */
+	protected string $alternativeHeadline;
+
+	public function getAlternativeHeadline():?string
+	{
+		return $this->alternativeHeadline??null;
+	}
+	public function setAlternativeHeadline(string $alternativeHeadline):void
+	{
+		$this->alternativeHeadline = $alternativeHeadline;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/amenityFeatureTrait.php b/inc/managers/SEO/render/Traits/_Properties/amenityFeatureTrait.php
new file mode 100644
index 0000000..3a72e23
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/amenityFeatureTrait.php
@@ -0,0 +1,40 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\LocationFeatureSpecification;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait amenityFeatureTrait {
+	use arrayHelper;
+	/**
+	 * @var LocationFeatureSpecification|array An amenity feature (e.g. a characteristic or service) of the Accommodation. This generic property does not make a statement about whether the feature is included in an offer for the main accommodation or available at extra costs.
+	 */
+	protected LocationFeatureSpecification|array $amenityFeature;
+
+	public function getAmenityFeature():LocationFeatureSpecification|array|null
+	{
+		return $this->amenityFeature??null;
+	}
+	public function setAmenityFeature(LocationFeatureSpecification|array $amenityFeature):void
+	{
+		if (is_array($amenityFeature)) {
+			$amenityFeature = $this->classArray('amenityFeature',$amenityFeature, 'JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\LocationFeatureSpecification');
+		}
+		$this->amenityFeature = $amenityFeature;
+	}
+
+	public function getAmenityFeatureFieldConfig():array
+	{
+		return [
+			'type'	=> 'repeater',
+			'label'	=> 'Amenity Feature',
+			'hint'	=> 'An amenity feature (e.g. a characteristic or service) of the Accommodation. This generic property does not make a statement about whether the feature is included in an offer for the main accommodation or available at extra costs.',
+			'fields'	=> [
+
+			]
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/amountOfThisGoodTrait.php b/inc/managers/SEO/render/Traits/_Properties/amountOfThisGoodTrait.php
new file mode 100644
index 0000000..7f31cba
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/amountOfThisGoodTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait amountOfThisGoodTrait {
+	/**
+	 * @var int|float The quantity of the goods included in the offer.
+	 */
+	protected int|float $amountOfThisGood;
+
+	public function getAmountOfThisGood():int|float|null
+	{
+		return $this->amountOfThisGood??null;
+	}
+	public function setAmountOfThisGood(int|float $amountOfThisGood):void
+	{
+		$this->amountOfThisGood = $amountOfThisGood;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/applicableCountryTrait.php b/inc/managers/SEO/render/Traits/_Properties/applicableCountryTrait.php
new file mode 100644
index 0000000..af6cdf0
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/applicableCountryTrait.php
@@ -0,0 +1,32 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Place\AdministrativeArea\Country;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait applicableCountryTrait {
+	use arrayHelper;
+
+	/**
+	 * @var Country|string|array A country where a particular merchant return policy applies to, for example the two-letter ISO 3166-1 alpha-2 country code.
+	 */
+	protected Country|string|array $applicableCountry;
+
+	public function getApplicableCountry():Country|string|array|null
+	{
+		return $this->applicableCountry??null;
+	}
+	public function setApplicableCountry(Country|string|array $applicableCountry):void
+	{
+		if(is_array($applicableCountry)){
+			$applicableCountry = $this->mixedArray('applicableCountry', $applicableCountry, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\Place\AdministrativeArea\Country'
+			]);
+		}
+		$this->applicableCountry = $applicableCountry;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/areaServedTrait.php b/inc/managers/SEO/render/Traits/_Properties/areaServedTrait.php
new file mode 100644
index 0000000..04a5ee4
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/areaServedTrait.php
@@ -0,0 +1,55 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\GeoShape;
+use JVBase\managers\SEO\render\Thing\Place\AdministrativeArea\AdministrativeArea;
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait areaServedTrait {
+	use arrayHelper;
+	/**
+	 * @var AdministrativeArea|GeoShape|Place|string|array The geographic area where a service or offered item is provided. Supersedes serviceArea.
+	 */
+	protected AdministrativeArea|GeoShape|Place|string|array $areaServed;
+
+	public function getAreaServed():AdministrativeArea|GeoShape|Place|string|array|null
+	{
+		return $this->areaServed??null;
+	}
+	public function setAreaServed(AdministrativeArea|GeoShape|Place|string|array $areaServed):void
+	{
+		if (is_array($areaServed)) {
+			$areaServed = $this->mixedArray('areaServed', $areaServed, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\GeoShape',
+				'JVBase\managers\SEO\render\Thing\Place\AdministrativeArea\AdministrativeArea',
+				'JVBase\managers\SEO\render\Thing\Place\Place'
+			]);
+		}
+		$this->areaServed = $areaServed;
+	}
+	public function getAreaServedFieldConfig():array
+	{
+		return [
+			'type'	=> 'group',
+			'wrap'	=> 'details',
+			'label'	=> 'Area(s) Served',
+			'fields'	=> [
+				'name'	=> [
+					'type'	=> 'text',
+					'label'	=> 'Area Name',
+					'hint'	=> 'neighbourhood, city, province, or country'
+				],
+				'sameAs'	=> [
+					'type'	=> 'url',
+					'label'	=> 'Same As',
+					'hint'	=> 'The Wikipedia page of this area'
+				]
+			]
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/associatedMediaTrait.php b/inc/managers/SEO/render/Traits/_Properties/associatedMediaTrait.php
new file mode 100644
index 0000000..d02e9a1
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/associatedMediaTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\MediaObject\MediaObject;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait associatedMediaTrait {
+	use arrayHelper;
+	/**
+	 * @var MediaObject|array A media object that encodes this CreativeWork. This property is a synonym for encoding.
+	 */
+	protected MediaObject|array $associatedMedia;
+
+	public function getAssociatedMedia():MediaObject|array|null
+	{
+		return $this->associatedMedia??null;
+	}
+	public function setAssociatedMedia(MediaObject|array $associatedMedia):void
+	{
+		if (is_array($associatedMedia)) {
+			$associatedMedia = $this->classArray('associatedMedia', $associatedMedia, 'JVBase\managers\SEO\render\Thing\CreativeWork\MediaObject\MediaObject');
+		}
+		$this->associatedMedia = $associatedMedia;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/attendeeTrait.php b/inc/managers/SEO/render/Traits/_Properties/attendeeTrait.php
new file mode 100644
index 0000000..3ba7a8e
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/attendeeTrait.php
@@ -0,0 +1,32 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait attendeeTrait {
+	use arrayHelper;
+	/**
+	 * @var Organization|Person|array A person or organization attending the event. Supersedes attendees.
+	 */
+	protected Organization|Person|array $attendee;
+
+	public function getAttendee():Organization|Person|array|null
+	{
+		return $this->attendee??null;
+	}
+	public function setAttendee(Organization|Person|array $attendee):void
+	{
+		if (is_array($attendee)) {
+			$attendee = $this->mixedArray('attendee', $attendee, [
+				'JVBase\managers\SEO\render\Thing\Organization\Organization',
+				'JVBase\managers\SEO\render\Thing\Person\Person'
+			]);
+		}
+		$this->attendee = $attendee;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/audienceTrait.php b/inc/managers/SEO/render/Traits/_Properties/audienceTrait.php
new file mode 100644
index 0000000..049bcac
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/audienceTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Audience;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait audienceTrait {
+	/**
+	 * @var Audience An intended audience, i.e. a group for whom something was created. Supersedes serviceAudience.
+	 */
+	protected Audience $audience;
+
+	public function getAudience():?Audience
+	{
+		return $this->audience??null;
+	}
+	public function setAudience(Audience $audience):void
+	{
+		$this->audience = $audience;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/audienceTypeTrait.php b/inc/managers/SEO/render/Traits/_Properties/audienceTypeTrait.php
new file mode 100644
index 0000000..6ba93d5
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/audienceTypeTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait audienceTypeTrait {
+	/**
+	 * @var string The target group associated with a given audience (e.g. veterans, car owners, musicians, etc.).
+	 */
+	protected string $audienceType;
+
+	public function getAudienceType():?string
+	{
+		return $this->audienceType??null;
+	}
+	public function setAudienceType(string $audienceType):void
+	{
+		$this->audienceType = $audienceType;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/audioTrait.php b/inc/managers/SEO/render/Traits/_Properties/audioTrait.php
new file mode 100644
index 0000000..ece6541
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/audioTrait.php
@@ -0,0 +1,34 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\MediaObject\AudioObject;
+use JVBase\managers\SEO\render\Thing\CreativeWork\MediaObject\Clip;
+use JVBase\managers\SEO\render\Thing\CreativeWork\MusicRecording;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait audioTrait {
+	use arrayHelper;
+	/**
+	 * @var AudioObject|Clip|MusicRecording|array An embedded audio object.
+	 */
+	protected AudioObject|Clip|MusicRecording|array $audio;
+
+	public function getAudio():AudioObject|Clip|MusicRecording|array|null
+	{
+		return $this->audio??null;
+	}
+	public function setAudio(AudioObject|Clip|MusicRecording|array $audio):void
+	{
+		if (is_array($audio)) {
+			$audio = $this->mixedArray('audio', $audio, [
+				'JVBase\managers\SEO\render\Thing\CreativeWork\MediaObject\AudioObject',
+				'JVBase\managers\SEO\render\Thing\CreativeWork\MediaObject\Clip',
+				'JVBase\managers\SEO\render\Thing\CreativeWork\MusicRecording'
+			]);
+		}
+		$this->audio = $audio;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/auditDateTrait.php b/inc/managers/SEO/render/Traits/_Properties/auditDateTrait.php
new file mode 100644
index 0000000..798a7f4
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/auditDateTrait.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\DataType\Date;
+use JVBase\managers\SEO\render\DataType\DateTime;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait auditDateTrait {
+	/**
+	 * @var Date|DateTime Date when a certification was last audited. See also gs1:certificationAuditDate.
+	 */
+	protected Date|DateTime $auditDate;
+
+	public function getAuditDate():Date|DateTime|null
+	{
+		return $this->auditDate??null;
+	}
+	public function setAuditDate(Date|DateTime $auditDate):void
+	{
+		$this->auditDate = $auditDate;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/authorTrait.php b/inc/managers/SEO/render/Traits/_Properties/authorTrait.php
new file mode 100644
index 0000000..fcece8c
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/authorTrait.php
@@ -0,0 +1,26 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait authorTrait {
+	use arrayHelper;
+	/**
+	 * @var Organization|Person|array The author of this content or rating. Please note that author is special in that HTML 5 provides a special mechanism for indicating authorship via the rel tag. That is equivalent to this and may be used interchangeably.
+	 */
+	protected Organization|Person|array $author;
+
+	public function getAuthor():Organization|Person|array|null
+	{
+		return $this->author??null;
+	}
+	public function setAuthor(Organization|Person|array $author):void
+	{
+		$this->author = $author;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/availabilityEndsTrait.php b/inc/managers/SEO/render/Traits/_Properties/availabilityEndsTrait.php
new file mode 100644
index 0000000..3116c0f
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/availabilityEndsTrait.php
@@ -0,0 +1,25 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\DataType\Date;
+use JVBase\managers\SEO\render\DataType\DateTime;
+use JVBase\managers\SEO\render\DataType\Time;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait availabilityEndsTrait {
+	/**
+	 * @var Date|DateTime|Time The end of the availability of the product or service included in the offer.
+	 */
+	protected Date|DateTime|Time $availabilityEnds;
+
+	public function getAvailabilityEnds():Date|DateTime|Time|null
+	{
+		return $this->availabilityEnds??null;
+	}
+	public function setAvailabilityEnds(Date|DateTime|Time $availabilityEnds):void
+	{
+		$this->availabilityEnds = $availabilityEnds;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/availabilityStartsTrait.php b/inc/managers/SEO/render/Traits/_Properties/availabilityStartsTrait.php
new file mode 100644
index 0000000..8d2e184
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/availabilityStartsTrait.php
@@ -0,0 +1,25 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\DataType\Date;
+use JVBase\managers\SEO\render\DataType\DateTime;
+use JVBase\managers\SEO\render\DataType\Time;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait availabilityStartsTrait {
+	/**
+	 * @var Date|DateTime|Time The end of the availability of the product or service included in the offer.
+	 */
+	protected Date|DateTime|Time $availabilityStarts;
+
+	public function getAvailabilityStarts():Date|DateTime|Time|null
+	{
+		return $this->availabilityStarts??null;
+	}
+	public function setAvailabilityStarts(Date|DateTime|Time $availabilityStarts):void
+	{
+		$this->availabilityStarts = $availabilityStarts;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/availabilityTrait.php b/inc/managers/SEO/render/Traits/_Properties/availabilityTrait.php
new file mode 100644
index 0000000..f059d0f
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/availabilityTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\ItemAvailability;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait availabilityTrait {
+	/**
+	 * @var ItemAvailability The availability of this item—for example In stock, Out of stock, Pre-order, etc.
+	 */
+	protected ItemAvailability $availability;
+
+	public function getAvailability():?ItemAvailability
+	{
+		return $this->availability??null;
+	}
+	public function setAvailability(ItemAvailability $availability):void
+	{
+		$this->availability = $availability;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/availableAtOrFromTrait.php b/inc/managers/SEO/render/Traits/_Properties/availableAtOrFromTrait.php
new file mode 100644
index 0000000..4522627
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/availableAtOrFromTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait availableAtOrFromTrait {
+	use arrayHelper;
+	/**
+	 * @var Place|array The place(s) from which the offer can be obtained (e.g. store locations).
+	 */
+	protected Place|array $availableAtOrFrom;
+
+	public function getAvailableAtOrFrom():Place|array|null
+	{
+		return $this->availableAtOrFrom??null;
+	}
+	public function setAvailableAtOrFrom(Place|array $availableAtOrFrom):void
+	{
+		if (is_array($availableAtOrFrom)) {
+			$availableAtOrFrom = $this->classArray('availableAtOrFrom', $availableAtOrFrom, 'JVBase\managers\SEO\render\Thing\Place\Place');
+		}
+		$this->availableAtOrFrom = $availableAtOrFrom;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/availableChannelTrait.php b/inc/managers/SEO/render/Traits/_Properties/availableChannelTrait.php
new file mode 100644
index 0000000..ffab850
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/availableChannelTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\ServiceChannel;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait availableChannelTrait {
+	/**
+	 * @var ServiceChannel A means of accessing the service (e.g. a phone bank, a web site, a location, etc.).
+	 */
+	protected ServiceChannel $availableChannel;
+
+	public function getAvailableChannel():?ServiceChannel
+	{
+		return $this->availableChannel??null;
+	}
+	public function setAvailableChannel(ServiceChannel $availableChannel):void
+	{
+		$this->availableChannel = $availableChannel;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/availableDeliveryMethodTrait.php b/inc/managers/SEO/render/Traits/_Properties/availableDeliveryMethodTrait.php
new file mode 100644
index 0000000..789d876
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/availableDeliveryMethodTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\DeliveryMethod;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait availableDeliveryMethodTrait {
+	/**
+	 * @var DeliveryMethod The delivery method(s) available for this offer.
+	 */
+	protected DeliveryMethod $availableDeliveryMethod;
+
+	public function getAvailableDeliveryMethod():?DeliveryMethod
+	{
+		return $this->availableDeliveryMethod??null;
+	}
+	public function setAvailableDeliveryMethod(DeliveryMethod $availableDeliveryMethod):void
+	{
+		$this->availableDeliveryMethod = $availableDeliveryMethod;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/availableLanguageTrait.php b/inc/managers/SEO/render/Traits/_Properties/availableLanguageTrait.php
new file mode 100644
index 0000000..62f4af4
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/availableLanguageTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Language;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait availableLanguageTrait {
+	use arrayHelper;
+	/**
+	 * @var Language|string|array A language someone may use with or at the item, service or place. Please use one of the language codes from the IETF BCP 47 standard. See also inLanguage.
+	 */
+	protected Language|string|array $availableLanguage;
+
+	public function getAvailableLanguage():Language|string|array|null
+	{
+		return $this->availableLanguage??null;
+	}
+	public function setAvailableLanguage(Language|string|array $availableLanguage):void
+	{
+		if (is_array($availableLanguage)){
+			$availableLanguage = $this->mixedArray('availableLanguage', $availableLanguage, ['string', 'JVBase\managers\SEO\render\Thing\Intangible\Language']);
+		}
+		$this->availableLanguage = $availableLanguage;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/awardTrait.php b/inc/managers/SEO/render/Traits/_Properties/awardTrait.php
new file mode 100644
index 0000000..b0a678a
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/awardTrait.php
@@ -0,0 +1,42 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait awardTrait {
+	use arrayHelper;
+	/**
+	 * @var array|string An award won by or for this item. Supersedes awards.
+	 */
+	protected array|string $award;
+
+	public function getAward():array|string|null
+	{
+		return $this->award??null;
+	}
+	public function setAward(array|string $award):void
+	{
+		if (is_array($award)){
+			$award = $this->stringArray('award',$award);
+		}
+		$this->award = $award;
+	}
+	public function getAwardFieldConfig():array
+	{
+		return [
+			'type' 		=> 'repeater',
+			'label'		=> 'Award(s)',
+			'hint'		=> 'List any awards won by this Thing.',
+			'fields'	=> [
+				'award'	=> [
+					'type'	=> 'text',
+					'label'	=> 'Award',
+					'hint'	=> 'Include the award name, year, and presenter (if any)'
+				]
+			]
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/bestRatingTrait.php b/inc/managers/SEO/render/Traits/_Properties/bestRatingTrait.php
new file mode 100644
index 0000000..1ee1c34
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/bestRatingTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait bestRatingTrait {
+	/**
+	 * @var int The highest value allowed in this rating system.
+	 */
+	protected int $bestRating = 5;
+
+	public function getBestRating():?int
+	{
+		return $this->bestRating??null;
+	}
+	public function setBestRating(int $bestRating):void
+	{
+		$this->bestRating = $bestRating;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/birthDateTrait.php b/inc/managers/SEO/render/Traits/_Properties/birthDateTrait.php
new file mode 100644
index 0000000..b4d77d4
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/birthDateTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\DataType\Date;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait birthDateTrait {
+	/**
+	 * @var Date Date of birth.
+	 */
+	protected Date $birthDate;
+
+	public function getBirthDate():?Date
+	{
+		return $this->birthDate??null;
+	}
+	public function setBirthDate(Date $birthDate):void
+	{
+		$this->birthDate = $birthDate;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/birthPlaceTrait.php b/inc/managers/SEO/render/Traits/_Properties/birthPlaceTrait.php
new file mode 100644
index 0000000..d6f5790
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/birthPlaceTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait birthPlaceTrait {
+	/**
+	 * @var Place The place where the person was born.
+	 */
+	protected Place $birthPlace;
+
+	public function getBirthPlace():?Place
+	{
+		return $this->birthPlace??null;
+	}
+	public function setBirthPlace(Place $birthPlace):void
+	{
+		$this->birthPlace = $birthPlace;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/bitrateTrait.php b/inc/managers/SEO/render/Traits/_Properties/bitrateTrait.php
new file mode 100644
index 0000000..02264c9
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/bitrateTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait bitrateTrait {
+	/**
+	 * @var string The bitrate of the media object.
+	 */
+	protected string $bitrate;
+
+	public function getBitrate():?string
+	{
+		return $this->bitrate??null;
+	}
+	public function setBitrate(string $bitrate):void
+	{
+		$this->bitrate = $bitrate;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/bookingTimeTrait.php b/inc/managers/SEO/render/Traits/_Properties/bookingTimeTrait.php
new file mode 100644
index 0000000..fb0e967
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/bookingTimeTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait bookingTimeTrait {
+	/**
+	 * @var string The date and time the reservation was booked.
+	 */
+	protected string $bookingTime;
+
+	public function getBookingTime():?string
+	{
+		return $this->bookingTime??null;
+	}
+	public function setBookingTime(string $bookingTime):void
+	{
+		$this->bookingTime = $bookingTime;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/boxTrait.php b/inc/managers/SEO/render/Traits/_Properties/boxTrait.php
new file mode 100644
index 0000000..14ba21c
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/boxTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait boxTrait {
+	/**
+	 * @var string A box is the area enclosed by the rectangle formed by two points. The first point is the lower corner, the second point is the upper corner. A box is expressed as two points separated by a space character.
+	 */
+	protected string $box;
+
+	public function getBox():?string
+	{
+		return $this->box??null;
+	}
+	public function setBox(string $box):void
+	{
+		$this->box = $box;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/branchCodeTrait.php b/inc/managers/SEO/render/Traits/_Properties/branchCodeTrait.php
new file mode 100644
index 0000000..9ec878e
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/branchCodeTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait branchCodeTrait {
+	/**
+	 * @var string A short textual code (also called "store code") that uniquely identifies a place of business. The code is typically assigned by the parentOrganization and used in structured URLs.
+	 *
+	 * For example, in the URL http://www.starbucks.co.uk/store-locator/etc/detail/3047 the code "3047" is a branchCode for a particular branch.
+	 */
+	protected string $branchCode;
+
+	public function getBranchCode():?string
+	{
+		return $this->branchCode??null;
+	}
+	public function setBranchCode(string $branchCode):void
+	{
+		$this->branchCode = $branchCode;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/brandTrait.php b/inc/managers/SEO/render/Traits/_Properties/brandTrait.php
new file mode 100644
index 0000000..f1eadbe
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/brandTrait.php
@@ -0,0 +1,32 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Brand\Brand;
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait brandTrait {
+	use arrayHelper;
+	/**
+	 * @var Brand|Organization|array The brand(s) associated with a product or service, or the brand(s) maintained by an organization or business person.
+	 */
+	protected Brand|Organization|array $brand;
+
+	public function getBrand():Brand|Organization|array|null
+	{
+		return $this->brand??null;
+	}
+	public function setBrand(Brand|Organization|array $brand):void
+	{
+		if (is_array($brand)){
+			$brand = $this->mixedArray('brand',$brand, [
+				'JVBase\managers\SEO\render\Thing\Intangible\Brand\Brand',
+				'JVBase\managers\SEO\render\Thing\Organization\Organization'
+			]);
+		}
+		$this->brand = $brand;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/breadcrumbTrait.php b/inc/managers/SEO/render/Traits/_Properties/breadcrumbTrait.php
new file mode 100644
index 0000000..d1acbde
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/breadcrumbTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\ItemList\BreadcrumbList;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait breadcrumbTrait {
+	/**
+	 * @var BreadcrumbList|string A set of links that can help a user understand and navigate a website hierarchy.
+	 */
+	protected BreadcrumbList|string $breadcrumb;
+
+	public function getBreadcrumb():BreadcrumbList|string|null
+	{
+		return $this->breadcrumb??null;
+	}
+	public function setBreadcrumb(BreadcrumbList|string $breadcrumb):void
+	{
+		$this->breadcrumb = $breadcrumb;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/brokerTrait.php b/inc/managers/SEO/render/Traits/_Properties/brokerTrait.php
new file mode 100644
index 0000000..3f76a27
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/brokerTrait.php
@@ -0,0 +1,32 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait brokerTrait {
+	use arrayHelper;
+	/**
+	 * @var Organization|Person|array An entity that arranges for an exchange between a buyer and a seller. In most cases a broker never acquires or releases ownership of a product or service involved in an exchange. If it is not clear whether an entity is a broker, seller, or buyer, the latter two terms are preferred. Supersedes bookingAgent.
+	 */
+	protected Organization|Person|array $broker;
+
+	public function getBroker():Organization|Person|array|null
+	{
+		return $this->broker??null;
+	}
+	public function setBroker(Organization|Person|array $broker):void
+	{
+		if (is_array($broker)) {
+			$broker = $this->mixedArray('broker', $broker, [
+				'JVBase\managers\SEO\render\Thing\Organization\Organization',
+				'JVBase\managers\SEO\render\Thing\Person\Person'
+			]);
+		}
+		$this->broker = $broker;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/businessDaysTrait.php b/inc/managers/SEO/render/Traits/_Properties/businessDaysTrait.php
new file mode 100644
index 0000000..88e15d5
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/businessDaysTrait.php
@@ -0,0 +1,32 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\DayOfWeek;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\OpeningHoursSpecification;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait businessDaysTrait {
+	use arrayHelper;
+	/**
+	 * @var DayOfWeek|OpeningHoursSpecification|array Days of the week when the merchant typically operates, indicated via opening hours markup.
+	 */
+	protected DayOfWeek|OpeningHoursSpecification|array $businessDaysTrait;
+
+	public function getBusinessDaysTrait():DayOfWeek|OpeningHoursSpecification|array|null
+	{
+		return $this->businessDaysTrait??null;
+	}
+	public function setBusinessDaysTrait(DayOfWeek|OpeningHoursSpecification|array $businessDaysTrait):void
+	{
+		if (is_array($businessDaysTrait)) {
+			$businessDaysTrait = $this->mixedArray('businessDaysTrait', $businessDaysTrait, [
+				'JVBase\managers\SEO\render\Thing\Intangible\Enumeration\DayOfWeek',
+				'JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\OpeningHoursSpecification'
+			]);
+		}
+		$this->businessDaysTrait = $businessDaysTrait;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/businessFunctionTrait.php b/inc/managers/SEO/render/Traits/_Properties/businessFunctionTrait.php
new file mode 100644
index 0000000..6113cab
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/businessFunctionTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\BusinessFunction;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait businessFunctionTrait {
+	/**
+	 * @var BusinessFunction The business function (e.g. sell, lease, repair, dispose) of the offer or component of a bundle (TypeAndQuantityNode). The default is http://purl.org/goodrelations/v1#Sell.
+	 */
+	protected BusinessFunction $businessFunction;
+
+	public function getBusinessFunction():?BusinessFunction
+	{
+		return $this->businessFunction??null;
+	}
+	public function setBusinessFunction(BusinessFunction $businessFunction):void
+	{
+		$this->businessFunction = $businessFunction;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/byArtistTrait.php b/inc/managers/SEO/render/Traits/_Properties/byArtistTrait.php
new file mode 100644
index 0000000..304ec34
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/byArtistTrait.php
@@ -0,0 +1,29 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait byArtistTrait {
+	use arrayHelper;
+	/**
+	 * @var Person|array The artist that performed this album or recording.
+	 * Note: Can also be MusicGroup, not included
+	 */
+	protected Person|array $byArtist;
+
+	public function getByArtist():Person|array|null
+	{
+		return $this->byArtist??null;
+	}
+	public function setByArtist(Person|array $byArtist):void
+	{
+		if (is_array($byArtist)) {
+			$byArtist = $this->classArray('byArtist', $byArtist, 'JVBase\managers\SEO\render\Thing\Person\Person');
+		}
+		$this->byArtist = $byArtist;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/byDayTrait.php b/inc/managers/SEO/render/Traits/_Properties/byDayTrait.php
new file mode 100644
index 0000000..c498166
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/byDayTrait.php
@@ -0,0 +1,31 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\DayOfWeek;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait byDayTrait {
+	use arrayHelper;
+	/**
+	 * @var DayOfWeek|string|array Defines the day(s) of the week on which a recurring Event takes place. May be specified using either DayOfWeek, or alternatively Text conforming to iCal's syntax for byDay recurrence rules.
+	 */
+	protected DayOfWeek|string|array $byDay;
+
+	public function getByDay():DayOfWeek|string|array|null
+	{
+		return $this->byDay??null;
+	}
+	public function setByDay(DayOfWeek|string|array $byDay):void
+	{
+		if (is_array($byDay)) {
+			$byDay = $this->mixedArray('byDay', $byDay,[
+				'string',
+				'JVBase\managers\SEO\render\Thing\Intangible\Enumeration\DayOfWeek'
+			]);
+		}
+		$this->byDay = $byDay;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/byMonthDayTrait.php b/inc/managers/SEO/render/Traits/_Properties/byMonthDayTrait.php
new file mode 100644
index 0000000..093b09d
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/byMonthDayTrait.php
@@ -0,0 +1,45 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait byMonthDayTrait {
+	use arrayHelper;
+	/**
+	 * @var int|array Defines the month(s) of the year on which a recurring Event takes place. Specified as an Integer between 1-12. January is 1.
+	 */
+	protected int|array $byMonthDay;
+
+	public function getByMonthDay():int|array|null
+	{
+		return $this->byMonthDay??null;
+	}
+	public function setByMonthDay(int|array $byMonthDay):void
+	{
+		if (is_array($byMonthDay)) {
+			$byMonthDay = $this->intArray('byMonthDay', $byMonthDay);
+		} else {
+			$byMonthDay = [$byMonthDay];
+		}
+		$byMonthDay = array_filter($byMonthDay,
+			function($day) {
+				if ($day === 0 || $day > 31) {
+					error_log('[SEO] Invalid month day number: '.$day);
+					return false;
+				}
+				return true;
+		});
+		if (empty($byMonthDay)){
+			error_log('[SEO]No byMonthDay values remaining');
+			return;
+		}
+		if (count($byMonthDay) === 1) {
+			$byMonthDay = $byMonthDay[0];
+		}
+
+		$this->byMonthDay = $byMonthDay;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/byMonthTrait.php b/inc/managers/SEO/render/Traits/_Properties/byMonthTrait.php
new file mode 100644
index 0000000..bb4022c
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/byMonthTrait.php
@@ -0,0 +1,49 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait byMonthTrait {
+	use arrayHelper;
+	/**
+	 * @var int|array Defines the month(s) of the year on which a recurring Event takes place. Specified as an Integer between 1-12. January is 1.
+	 */
+	protected int|array $byMonth;
+
+	public function getByMonth():int|array|null
+	{
+		return $this->byMonth??null;
+	}
+	public function setByMonth(int|array $byMonth):void
+	{
+		if (is_array($byMonth)) {
+			$byMonth = $this->intArray('byMonth', $byMonth);
+		} else {
+			$byMonth = [$byMonth];
+		}
+		$byMonth = array_filter($byMonth,
+			function($month) {
+				if ($month === 0 || $month > 12) {
+					error_log('[SEO] Invalid month number: '.$month);
+					return false;
+				}
+				return true;
+			});
+		if (empty($byMonth)){
+			error_log('[SEO]No byMonth values remaining');
+			return;
+		}
+		if (count($byMonth) === 1) {
+			$byMonth = $byMonth[0];
+		}
+
+		if ($byMonth === 0 || $byMonth > 12) {
+			error_log('[SEO] Invalid month number: '.$byMonth);
+			return;
+		}
+		$this->byMonth = $byMonth;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/byMonthWeekTrait.php b/inc/managers/SEO/render/Traits/_Properties/byMonthWeekTrait.php
new file mode 100644
index 0000000..78eba00
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/byMonthWeekTrait.php
@@ -0,0 +1,49 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait byMonthWeekTrait {
+	use arrayHelper;
+	/**
+	 * @var int|array Defines the month(s) of the year on which a recurring Event takes place. Specified as an Integer between 1-12. January is 1.
+	 */
+	protected int|array $byMonthWeek;
+
+	public function getByMonthWeek():int|array|null
+	{
+		return $this->byMonthWeek??null;
+	}
+	public function setByMonthWeek(int|array $byMonthWeek):void
+	{
+		if (is_array($byMonthWeek)) {
+			$byMonthWeek = $this->intArray('byMonthWeek', $byMonthWeek);
+		} else {
+			$byMonthWeek = [$byMonthWeek];
+		}
+		$byMonthWeek = array_filter($byMonthWeek,
+			function($week) {
+				if ($week === 0 || $week > 6) {
+					error_log('[SEO] Invalid month number: '.$month);
+					return false;
+				}
+				return true;
+			});
+		if (empty($byMonthWeek)){
+			error_log('[SEO]No byMonthWeek values remaining');
+			return;
+		}
+		if (count($byMonthWeek) === 1) {
+			$byMonthWeek = $byMonthWeek[0];
+		}
+
+		if ($byMonthWeek === 0 || $byMonthWeek > 12) {
+			error_log('[SEO] Invalid month number: '.$byMonthWeek);
+			return;
+		}
+		$this->byMonthWeek = $byMonthWeek;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/caloriesTrait.php b/inc/managers/SEO/render/Traits/_Properties/caloriesTrait.php
new file mode 100644
index 0000000..6639bc1
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/caloriesTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait caloriesTrait {
+	/**
+	 * @var int|float The number of calories.
+	 */
+	protected int|float $calories;
+
+	public function getCalories():int|float|null
+	{
+		return $this->calories??null;
+	}
+	public function setCalories(int|float $calories):void
+	{
+		$this->calories = $calories;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/captionTrait.php b/inc/managers/SEO/render/Traits/_Properties/captionTrait.php
new file mode 100644
index 0000000..b51d923
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/captionTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait captionTrait {
+	/**
+	 * @var string The caption for this object
+	 */
+	protected string $caption;
+
+	public function getCaption():?string
+	{
+		return $this->caption??null;
+	}
+	public function setCaption(string $caption):void
+	{
+		$this->caption = $caption;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/carbohydrateContentTrait.php b/inc/managers/SEO/render/Traits/_Properties/carbohydrateContentTrait.php
new file mode 100644
index 0000000..2ebbe45
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/carbohydrateContentTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait carbohydrateContentTrait {
+	/**
+	 * @var int|float The number of grams of carbohydrates
+	 */
+	protected int|float $carbohydrateContent;
+
+	public function getCarbohydrateContent():int|float|null
+	{
+		return $this->carbohydrateContent??null;
+	}
+	public function setCarbohydrateContent(int|float $carbohydrateContent):void
+	{
+		$this->carbohydrateContent = $carbohydrateContent;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/categoryTrait.php b/inc/managers/SEO/render/Traits/_Properties/categoryTrait.php
new file mode 100644
index 0000000..7c9fd75
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/categoryTrait.php
@@ -0,0 +1,35 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\CategoryCode;
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\PhysicalActivityCategory;
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait categoryTrait {
+	use arrayHelper;
+	/**
+	 * @var CategoryCode|PhysicalActivityCategory|Thing|string|array A category for the item. Greater signs or slashes can be used to informally indicate a category hierarchy.
+	 */
+	protected CategoryCode|PhysicalActivityCategory|Thing|string|array $category;
+
+	public function getCategory():CategoryCode|PhysicalActivityCategory|Thing|string|array|null
+	{
+		return $this->category??null;
+	}
+	public function setCategory(CategoryCode|PhysicalActivityCategory|Thing|string|array $category):void
+	{
+		if (is_array($category)) {
+			$category = $this->mixedArray('category', $category, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\Thing',
+				'JVBase\managers\SEO\render\Thing\Intangible\CategoryCode',
+				'JVBase\managers\SEO\render\Thing\Intangible\Enumeration\PhysicalActivityCategory'
+			]);
+		}
+		$this->category = $category;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/certificationIdentificationTrait.php b/inc/managers/SEO/render/Traits/_Properties/certificationIdentificationTrait.php
new file mode 100644
index 0000000..79da851
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/certificationIdentificationTrait.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\DefinedTerm;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait certificationIdentificationTrait {
+	/**
+	 * @var DefinedTerm|string Identifier of a certification instance (as registered with an independent certification body). Typically this identifier can be used to consult and verify the certification instance. See also gs1:certificationIdentification.
+	 */
+	protected DefinedTerm|string $certificationIdentification;
+
+	public function getCertificationIdentification():DefinedTerm|string|null
+	{
+		return $this->certificationIdentification??null;
+	}
+	public function setCertificationIdentification(DefinedTerm|string $certificationIdentification):void
+	{
+		$this->certificationIdentification = $certificationIdentification;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/certificationRatingTrait.php b/inc/managers/SEO/render/Traits/_Properties/certificationRatingTrait.php
new file mode 100644
index 0000000..e4f4850
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/certificationRatingTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Rating\Rating;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait certificationRatingTrait {
+	/**
+	 * @var Rating Rating of a certification instance (as defined by an independent certification body). Typically this rating can be used to rate the level to which the requirements of the certification instance are fulfilled. See also gs1:certificationValue.
+	 */
+	protected Rating $certificationRating;
+
+	public function getCertificationRating():?Rating
+	{
+		return $this->certificationRating??null;
+	}
+	public function setCertificationRating(Rating $certificationRating):void
+	{
+		$this->certificationRating = $certificationRating;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/certificationStatusTrait.php b/inc/managers/SEO/render/Traits/_Properties/certificationStatusTrait.php
new file mode 100644
index 0000000..0f33773
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/certificationStatusTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\CertificationStatusEnumeration;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait certificationStatusTrait {
+	/**
+	 * @var CertificationStatusEnumeration Indicates the current status of a certification: active or inactive. See also gs1:certificationStatus.
+	 */
+	protected CertificationStatusEnumeration $certificationStatus;
+
+	public function getCertificationStatus():?CertificationStatusEnumeration
+	{
+		return $this->certificationStatus??null;
+	}
+	public function setCertificationStatus(CertificationStatusEnumeration $certificationStatus):void
+	{
+		$this->certificationStatus = $certificationStatus;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/characterTrait.php b/inc/managers/SEO/render/Traits/_Properties/characterTrait.php
new file mode 100644
index 0000000..35a4735
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/characterTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait characterTrait {
+	use arrayHelper;
+	/**
+	 * @var Person|array Fictional person connected with a creative work.
+	 */
+	protected Person|array $character;
+
+	public function getCharacter():Person|array|null
+	{
+		return $this->character??null;
+	}
+	public function setCharacter(Person|array $character):void
+	{
+		if (is_array($character)) {
+			$character = $this->classArray('character', $character, 'JVBase\managers\SEO\render\Thing\Person\Person');
+		}
+		$this->character = $character;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/checkoutPageURLTemplateTrait.php b/inc/managers/SEO/render/Traits/_Properties/checkoutPageURLTemplateTrait.php
new file mode 100644
index 0000000..f85ce1c
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/checkoutPageURLTemplateTrait.php
@@ -0,0 +1,22 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait checkoutPageURLTemplateTrait {
+	/**
+	 * @var string A URL template (RFC 6570) for a checkout page for an offer. This approach allows merchants to specify a URL for online checkout of the offered product, by interpolating parameters such as the logged in user ID, product ID, quantity, discount code etc. Parameter naming and standardization are not specified here.
+	 * Example: https://www.example.com/checkout?items={VARIANT_ID_1}:{Quantity_1},{VARIANT_ID_2}:{Quantity_2}&discount={DISCOUNT_CODE}&store_id={pickup_store_id}
+	 */
+	protected string $checkoutPageURLTemplate;
+
+	public function getCheckoutPageURLTemplate():?string
+	{
+		return $this->checkoutPageURLTemplate??null;
+	}
+	public function setCheckoutPageURLTemplate(string $checkoutPageURLTemplate):void
+	{
+		$this->checkoutPageURLTemplate = $checkoutPageURLTemplate;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/childrenTrait.php b/inc/managers/SEO/render/Traits/_Properties/childrenTrait.php
new file mode 100644
index 0000000..b021419
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/childrenTrait.php
@@ -0,0 +1,31 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait childrenTrait {
+	use arrayHelper;
+	/**
+	 * @var Person|string|array A child of the person.
+	 */
+	protected Person|string|array $children;
+
+	public function getChildren():Person|string|array|null
+	{
+		return $this->children??null;
+	}
+	public function setChildren(Person|string|array $children):void
+	{
+		if (is_array($children)) {
+			$children = $this->mixedArray('children', $children, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\Person\Person'
+			]);
+		}
+		$this->children = $children;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/cholesterolContentTrait.php b/inc/managers/SEO/render/Traits/_Properties/cholesterolContentTrait.php
new file mode 100644
index 0000000..7f67593
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/cholesterolContentTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait cholesterolContentTrait {
+	/**
+	 * @var int|float The number of milligrams of cholesterol
+	 */
+	protected int|float $cholesterolContent;
+
+	public function getCholesterolContent():int|float|null
+	{
+		return $this->cholesterolContent??null;
+	}
+	public function setCholesterolContent(int|float $cholesterolContent):void
+	{
+		$this->cholesterolContent = $cholesterolContent;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/circleTrait.php b/inc/managers/SEO/render/Traits/_Properties/circleTrait.php
new file mode 100644
index 0000000..464ff88
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/circleTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait circleTrait {
+	/**
+	 * @var string A circle is the circular region of a specified radius centered at a specified latitude and longitude. A circle is expressed as a pair followed by a radius in meters.
+	 */
+	protected string $circle;
+
+	public function getCircle():?string
+	{
+		return $this->circle??null;
+	}
+	public function setCircle(string $circle):void
+	{
+		$this->circle = $circle;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/citationTrait.php b/inc/managers/SEO/render/Traits/_Properties/citationTrait.php
new file mode 100644
index 0000000..79dad14
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/citationTrait.php
@@ -0,0 +1,31 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait citationTrait {
+	use arrayHelper;
+	/**
+	 * @var CreativeWork|string|array A citation or reference to another creative work, such as another publication, web page, scholarly article, etc.
+	 */
+	protected CreativeWork|string|array $citation;
+
+	public function getCitation():CreativeWork|string|array|null
+	{
+		return $this->citation??null;
+	}
+	public function setCitation(CreativeWork|string|array $citation):void
+	{
+		if (is_array($citation)) {
+			$citation = $this->mixedArray('citation', $citation, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork'
+			]);
+		}
+		$this->citation = $citation;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/clipNumberTrait.php b/inc/managers/SEO/render/Traits/_Properties/clipNumberTrait.php
new file mode 100644
index 0000000..0257698
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/clipNumberTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait clipNumberTrait {
+	/**
+	 * @var int|string Position of the clip within an ordered group of clips.
+	 */
+	protected int|string $clipNumber;
+
+	public function getClipNumber():int|string|null
+	{
+		return $this->clipNumber??null;
+	}
+	public function setClipNumber(int|string $clipNumber):void
+	{
+		$this->clipNumber = $clipNumber;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/closesTrait.php b/inc/managers/SEO/render/Traits/_Properties/closesTrait.php
new file mode 100644
index 0000000..9029118
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/closesTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\DataType\Time;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait closesTrait {
+	/**
+	 * @var Time The closing hour of the place or service on the given day(s) of the week
+	 */
+	protected Time $closes;
+
+	public function getCloses():?Time
+	{
+		return $this->closes??null;
+	}
+	public function setCloses(Time $closes):void
+	{
+		$this->closes = $closes;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/codeValueTrait.php b/inc/managers/SEO/render/Traits/_Properties/codeValueTrait.php
new file mode 100644
index 0000000..e31b387
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/codeValueTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait codeValueTrait {
+	/**
+	 * @var string A short textual code that uniquely identifies the value.
+	 */
+	protected string $codeValue;
+
+	public function getCodeValue():?string
+	{
+		return $this->codeValue??null;
+	}
+	public function setCodeValue(string $codeValue):void
+	{
+		$this->codeValue = $codeValue;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/colleagueTrait.php b/inc/managers/SEO/render/Traits/_Properties/colleagueTrait.php
new file mode 100644
index 0000000..31410e4
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/colleagueTrait.php
@@ -0,0 +1,31 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait colleagueTrait {
+	use arrayHelper;
+	/**
+	 * @var Person|string|array A colleague of the person. Supersedes colleagues.
+	 */
+	protected Person|string|array $colleague;
+
+	public function getColleague():Person|string|array|null
+	{
+		return $this->colleague??null;
+	}
+	public function setColleague(Person|string|array $colleague):void
+	{
+		if (is_array($colleague)) {
+			$colleague = $this->mixedArray('colleague', $colleague, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\Person\Person'
+			]);
+		}
+		$this->colleague = $colleague;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/colorTrait.php b/inc/managers/SEO/render/Traits/_Properties/colorTrait.php
new file mode 100644
index 0000000..e19c50c
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/colorTrait.php
@@ -0,0 +1,27 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait colorTrait {
+	use arrayHelper;
+	/**
+	 * @var array|string The color of the product.
+	 */
+	protected array|string $color;
+
+	public function getColor():array|string|null
+	{
+		return $this->color??null;
+	}
+	public function setColor(array|string $color):void
+	{
+		if (is_array($color)) {
+			$color = $this->stringArray('color', $color);
+		}
+		$this->color = $color;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/companyRegistrationTrait.php b/inc/managers/SEO/render/Traits/_Properties/companyRegistrationTrait.php
new file mode 100644
index 0000000..fa3c1de
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/companyRegistrationTrait.php
@@ -0,0 +1,26 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\Certification;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait companyRegistrationTrait {
+	/**
+	 * @var Certification A companyRegistration of the item
+	 */
+	protected Certification $companyRegistration;
+
+	public function getCompanyRegistration():?Certification
+	{
+		return $this->companyRegistration??null;
+	}
+	public function setCompanyRegistration(array|Certification $companyRegistration):void
+	{
+		if (is_array($companyRegistration)) {
+			$companyRegistration = Certification::fromArray($companyRegistration);
+		}
+		$this->companyRegistration = $companyRegistration;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/composerTrait.php b/inc/managers/SEO/render/Traits/_Properties/composerTrait.php
new file mode 100644
index 0000000..845a5ab
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/composerTrait.php
@@ -0,0 +1,32 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait composerTrait {
+	use arrayHelper;
+	/**
+	 * @var Organization|Person|array The person or organization who wrote a composition, or who is the composer of a work performed at some event.
+	 */
+	protected Organization|Person|array $composer;
+
+	public function getComposer():Organization|Person|array|null
+	{
+		return $this->composer??null;
+	}
+	public function setComposer(Organization|Person|array $composer):void
+	{
+		if (is_array($composer)) {
+			$composer = $this->mixedArray('composer', $composer, [
+				'JVBase\managers\SEO\render\Thing\Organization\Organization',
+				'JVBase\managers\SEO\render\Thing\Person\Person'
+			]);
+		}
+		$this->composer = $composer;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/contactOptionTrait.php b/inc/managers/SEO/render/Traits/_Properties/contactOptionTrait.php
new file mode 100644
index 0000000..c7d77e7
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/contactOptionTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\ContactPointOption;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait contactOptionTrait {
+	/**
+	 * @var ContactPointOption An option available on this contact point (e.g. a toll-free number or support for hearing-impaired callers).
+	 */
+	protected ContactPointOption $contactOption;
+
+	public function getContactOption():?ContactPointOption
+	{
+		return $this->contactOption??null;
+	}
+	public function setContactOption(ContactPointOption $contactOption):void
+	{
+		$this->contactOption = $contactOption;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/contactPointTrait.php b/inc/managers/SEO/render/Traits/_Properties/contactPointTrait.php
new file mode 100644
index 0000000..b82887f
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/contactPointTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\ContactPoint;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait contactPointTrait {
+	use arrayHelper;
+	/**
+	 * @var ContactPoint|array A contact point for a person or organization. Supersedes contactPoints.
+	 */
+	protected ContactPoint|array $contactPoint;
+
+	public function getContactPoint():ContactPoint|array|null
+	{
+		return $this->contactPoint??null;
+	}
+	public function setContactPoint(ContactPoint|array $contactPoint):void
+	{
+		if (is_array($contactPoint)){
+			$contactPoint = $this->classArray('contactPoint', $contactPoint, 'JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\ContactPoint');
+		}
+		$this->contactPoint = $contactPoint;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/contactTypeTrait.php b/inc/managers/SEO/render/Traits/_Properties/contactTypeTrait.php
new file mode 100644
index 0000000..5195b3b
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/contactTypeTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait contactTypeTrait {
+	/**
+	 * @var string A person or organization can have different contact points, for different purposes. For example, a sales contact point, a PR contact point and so on. This property is used to specify the kind of contact point.
+	 */
+	protected string $contactType;
+
+	public function getContactType():?string
+	{
+		return $this->contactType??null;
+	}
+	public function setContactType(string $contactType):void
+	{
+		$this->contactType = $contactType;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/containedInPlaceTrait.php b/inc/managers/SEO/render/Traits/_Properties/containedInPlaceTrait.php
new file mode 100644
index 0000000..00d2eba
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/containedInPlaceTrait.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait containedInPlaceTrait {
+	/**
+	 * @var Place The basic containment relation between a place and one that contains it. Supersedes containedIn.
+	 * Inverse property: containsPlace
+	 */
+	protected Place $containedInPlace;
+
+	public function getContainedInPlace():?Place
+	{
+		return $this->containedInPlace??null;
+	}
+	public function setContainedInPlace(Place $containedInPlace):void
+	{
+		$this->containedInPlace = $containedInPlace;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/contentLocationTrait.php b/inc/managers/SEO/render/Traits/_Properties/contentLocationTrait.php
new file mode 100644
index 0000000..058e41d
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/contentLocationTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait contentLocationTrait {
+	use arrayHelper;
+	/**
+	 * @var Place|array The location depicted or described in the content. For example, the location in a photograph or painting.
+	 */
+	protected Place|array $contentLocation;
+
+	public function getContentLocation():Place|array|null
+	{
+		return $this->contentLocation??null;
+	}
+	public function setContentLocation(Place|array $contentLocation):void
+	{
+		if (is_array($contentLocation)) {
+			$contentLocation = $this->classArray('contentLocation', $contentLocation, 'JVBase\managers\SEO\render\Thing\Place\Place');
+		}
+		$this->contentLocation = $contentLocation;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/contentRatingTrait.php b/inc/managers/SEO/render/Traits/_Properties/contentRatingTrait.php
new file mode 100644
index 0000000..86d6c75
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/contentRatingTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait contentRatingTrait {
+	/**
+	 * @var string Official rating of a piece of content—for example, 'MPAA PG-13'.
+	 */
+	protected string $contentRating;
+
+	public function getContentRating():?string
+	{
+		return $this->contentRating??null;
+	}
+	public function setContentRating(string $contentRating):void
+	{
+		$this->contentRating = $contentRating;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/contentReferenceTimeTrait.php b/inc/managers/SEO/render/Traits/_Properties/contentReferenceTimeTrait.php
new file mode 100644
index 0000000..22e0c69
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/contentReferenceTimeTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\DataType\DateTime;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait contentReferenceTimeTrait {
+	use arrayHelper;
+	/**
+	 * @var DateTime|array The specific time described by a creative work, for works (e.g. articles, video objects etc.) that emphasise a particular moment within an Event.
+	 */
+	protected DateTime|array $contentReferenceTime;
+
+	public function getContentReferenceTime():DateTime|array|null
+	{
+		return $this->contentReferenceTime??null;
+	}
+	public function setContentReferenceTime(DateTime|array $contentReferenceTime):void
+	{
+		if (is_array($contentReferenceTime)) {
+			$contentReferenceTime = $this->classArray('contentReferenceTime', $contentReferenceTime, 'JVBase\managers\SEO\render\DataType\DateTime');
+		}
+		$this->contentReferenceTime = $contentReferenceTime;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/contentSizeTrait.php b/inc/managers/SEO/render/Traits/_Properties/contentSizeTrait.php
new file mode 100644
index 0000000..e7ebb3f
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/contentSizeTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait contentSizeTrait {
+	/**
+	 * @var string File size in (mega/kilo)bytes.
+	 */
+	protected string $contentSize;
+
+	public function getContentSize():?string
+	{
+		return $this->contentSize??null;
+	}
+	public function setContentSize(string $contentSize):void
+	{
+		$this->contentSize = $contentSize;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/contentTypeTrait.php b/inc/managers/SEO/render/Traits/_Properties/contentTypeTrait.php
new file mode 100644
index 0000000..6574e4e
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/contentTypeTrait.php
@@ -0,0 +1,27 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait contentTypeTrait {
+	use arrayHelper;
+	/**
+	 * @var string|array The supported content type(s) for an EntryPoint response.
+	 */
+	protected string|array $contentType;
+
+	public function getContentType():string|array|null
+	{
+		return $this->contentType??null;
+	}
+	public function setContentType(string|array $contentType):void
+	{
+		if (is_array($contentType)) {
+			$contentType = $this->stringArray('contentType', $contentType);
+		}
+		$this->contentType = $contentType;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/contentUrlTrait.php b/inc/managers/SEO/render/Traits/_Properties/contentUrlTrait.php
new file mode 100644
index 0000000..e533835
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/contentUrlTrait.php
@@ -0,0 +1,25 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait contentUrlTrait {
+	/**
+	 * @var string Actual bytes of the media object, for example the image file or video file.
+	 */
+	protected string $contentUrl;
+
+	public function getContentUrl():?string
+	{
+		return $this->contentUrl??null;
+	}
+	public function setContentUrl(string $contentUrl):void
+	{
+		if (!filter_var($contentUrl, FILTER_VALIDATE_URL)) {
+			error_log('[SEO]contentUrl is expected to be a valid url to a file.');
+			return;
+		}
+		$this->contentUrl = $contentUrl;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/contributorTrait.php b/inc/managers/SEO/render/Traits/_Properties/contributorTrait.php
new file mode 100644
index 0000000..a4218ad
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/contributorTrait.php
@@ -0,0 +1,32 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait contributorTrait {
+	use arrayHelper;
+	/**
+	 * @var Organization|Person|array A secondary contributor to the CreativeWork or Event.
+	 */
+	protected Organization|Person|array $contributor;
+
+	public function getContributor():Organization|Person|array|null
+	{
+		return $this->contributor??null;
+	}
+	public function setContributor(Organization|Person|array $contributor):void
+	{
+		if (is_array($contributor)) {
+			$contributor = $this->mixedArray('contributor', $contributor, [
+				'JVBase\managers\SEO\render\Thing\Organization\Organization',
+				'JVBase\managers\SEO\render\Thing\Person\Person'
+			]);
+		}
+		$this->contributor = $contributor;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/copyrightHolderTrait.php b/inc/managers/SEO/render/Traits/_Properties/copyrightHolderTrait.php
new file mode 100644
index 0000000..1b2e485
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/copyrightHolderTrait.php
@@ -0,0 +1,32 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait copyrightHolderTrait {
+	use arrayHelper;
+	/**
+	 * @var Organization|Person|array The party holding the legal copyright to the CreativeWork.
+	 */
+	protected Organization|Person|array $copyrightHolder;
+
+	public function getCopyrightHolder():Organization|Person|array|null
+	{
+		return $this->copyrightHolder??null;
+	}
+	public function setCopyrightHolder(Organization|Person|array $copyrightHolder):void
+	{
+		if (is_array($copyrightHolder)) {
+			$copyrightHolder = $this->mixedArray('copyrightHolder', $copyrightHolder, [
+				'JVBase\managers\SEO\render\Thing\Organization\Organization',
+				'JVBase\managers\SEO\render\Thing\Person\Person'
+			]);
+		}
+		$this->copyrightHolder = $copyrightHolder;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/copyrightNoticeTrait.php b/inc/managers/SEO/render/Traits/_Properties/copyrightNoticeTrait.php
new file mode 100644
index 0000000..4ef3fb1
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/copyrightNoticeTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait copyrightNoticeTrait {
+	/**
+	 * @var string Text of a notice appropriate for describing the copyright aspects of this Creative Work, ideally indicating the owner of the copyright for the Work.
+	 */
+	protected string $copyrightNotice;
+
+	public function getCopyrightNotice():?string
+	{
+		return $this->copyrightNotice??null;
+	}
+	public function setCopyrightNotice(string $copyrightNotice):void
+	{
+		$this->copyrightNotice = $copyrightNotice;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/copyrightYearTrait.php b/inc/managers/SEO/render/Traits/_Properties/copyrightYearTrait.php
new file mode 100644
index 0000000..e3d7314
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/copyrightYearTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait copyrightYearTrait {
+	/**
+	 * @var int The year during which the claimed copyright for the CreativeWork was first asserted.
+	 */
+	protected int $copyrightYear;
+
+	public function getCopyrightYear():?int
+	{
+		return $this->copyrightYear??null;
+	}
+	public function setCopyrightYear(int $copyrightYear):void
+	{
+		$this->copyrightYear = $copyrightYear;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/countryOfAssemblyTrait.php b/inc/managers/SEO/render/Traits/_Properties/countryOfAssemblyTrait.php
new file mode 100644
index 0000000..e1d3161
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/countryOfAssemblyTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait countryOfAssemblyTrait {
+	/**
+	 * @var string The place where the product was assembled.
+	 */
+	protected string $countryOfAssembly;
+
+	public function getCountryOfAssembly():?string
+	{
+		return $this->countryOfAssembly??null;
+	}
+	public function setCountryOfAssembly(string $countryOfAssembly):void
+	{
+		$this->countryOfAssembly = $countryOfAssembly;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/countryOfOriginTrait.php b/inc/managers/SEO/render/Traits/_Properties/countryOfOriginTrait.php
new file mode 100644
index 0000000..a064e16
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/countryOfOriginTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait countryOfOriginTrait {
+	/**
+	 * @var string The country of origin of something, including products as well as creative works such as movie and TV content.
+	 */
+	protected string $countryOfOrigin;
+
+	public function getCountryOfOrigin():?string
+	{
+		return $this->countryOfOrigin??null;
+	}
+	public function setCountryOfOrigin(string $countryOfOrigin):void
+	{
+		$this->countryOfOrigin = $countryOfOrigin;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/countryTrait.php b/inc/managers/SEO/render/Traits/_Properties/countryTrait.php
new file mode 100644
index 0000000..de0d2a5
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/countryTrait.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Place\AdministrativeArea\Country;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait countryTrait {
+
+	/**
+	 * @var Country|string The country. Recommended to be in 2-letter ISO 3166-1 alpha-2 format, for example "US". For backward compatibility, a 3-letter ISO 3166-1 alpha-3 country code such as "SGP" or a full country name such as "Singapore" can also be used.
+	 */
+	protected Country|string $Country;
+
+	public function getCountry():Country|string|null
+	{
+		return $this->Country??null;
+	}
+	public function setCountry(Country|string $Country):void
+	{
+		$this->Country = $Country;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/creativeWorkStatusTrait.php b/inc/managers/SEO/render/Traits/_Properties/creativeWorkStatusTrait.php
new file mode 100644
index 0000000..e24acb4
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/creativeWorkStatusTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\DefinedTerm;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait creativeWorkStatusTrait {
+	/**
+	 * @var DefinedTerm|string The status of a creative work in terms of its stage in a lifecycle. Example terms include Incomplete, Draft, Published, Obsolete. Some organizations define a set of terms for the stages of their publication lifecycle.
+	 */
+	protected DefinedTerm|string $creativeWorkStatus;
+
+	public function getCreativeWorkStatus():DefinedTerm|string|null
+	{
+		return $this->creativeWorkStatus??null;
+	}
+	public function setCreativeWorkStatus(DefinedTerm|string $creativeWorkStatus):void
+	{
+		$this->creativeWorkStatus = $creativeWorkStatus;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/creatorTrait.php b/inc/managers/SEO/render/Traits/_Properties/creatorTrait.php
new file mode 100644
index 0000000..738e5b7
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/creatorTrait.php
@@ -0,0 +1,32 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait creatorTrait {
+	use arrayHelper;
+	/**
+	 * @var Organization|Person|array The creator/author of this CreativeWork. This is the same as the Author property for CreativeWork.
+	 */
+	protected Organization|Person|array $creator;
+
+	public function getCreator():Organization|Person|array|null
+	{
+		return $this->creator??null;
+	}
+	public function setCreator(Organization|Person|array $creator):void
+	{
+		if (is_array($creator)) {
+			$creator = $this->mixedArray('creator', $creator, [
+				'JVBase\managers\SEO\render\Thing\Organization\Organization',
+				'JVBase\managers\SEO\render\Thing\Person\Person'
+			]);
+		}
+		$this->creator = $creator;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/creditTextTrait.php b/inc/managers/SEO/render/Traits/_Properties/creditTextTrait.php
new file mode 100644
index 0000000..86ed1f9
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/creditTextTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait creditTextTrait {
+	/**
+	 * @var string Text that can be used to credit person(s) and/or organization(s) associated with a published Creative Work.
+	 */
+	protected string $creditText;
+
+	public function getCreditText():?string
+	{
+		return $this->creditText??null;
+	}
+	public function setCreditText(string $creditText):void
+	{
+		$this->creditText = $creditText;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/currenciesAcceptedTrait.php b/inc/managers/SEO/render/Traits/_Properties/currenciesAcceptedTrait.php
new file mode 100644
index 0000000..5bf4956
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/currenciesAcceptedTrait.php
@@ -0,0 +1,39 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait currenciesAcceptedTrait {
+	use arrayHelper;
+	/**
+	 * @var string|array The currency accepted.
+	 *
+	 * Use standard formats: ISO 4217 currency format, e.g. "USD"; Ticker symbol for cryptocurrencies, e.g. "BTC"; well known names for Local Exchange Trading Systems (LETS) and other currency types, e.g. "Ithaca HOUR".
+	 */
+	protected string|array $currenciesAccepted;
+
+	public function getCurrenciesAccepted():string|array|null
+	{
+		return $this->currenciesAccepted??null;
+	}
+	public function setCurrenciesAccepted(string|array $currenciesAccepted):void
+	{
+		if (is_array($currenciesAccepted)) {
+			$currenciesAccepted = $this->stringArray('currenciesAccepted', $currenciesAccepted);
+		}
+		$this->currenciesAccepted = $currenciesAccepted;
+	}
+
+	public function getCurrenciesAcceptedFieldConfig():array
+	{
+		return [
+			'type'	=> 'text',
+			'label'	=> 'Currencies Accepted',
+			'hint'	=> 'A comma separated list of currencies (ex: CAD, USD)',
+			'default' => 'CAD'
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/currencyTrait.php b/inc/managers/SEO/render/Traits/_Properties/currencyTrait.php
new file mode 100644
index 0000000..ae58cc1
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/currencyTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait currencyTrait {
+	/**
+	 * @var string The currency in which the monetary amount is expressed.
+	 *
+	 * Use standard formats: ISO 4217 currency format, e.g. "USD"; Ticker symbol for cryptocurrencies, e.g. "BTC"; well known names for Local Exchange Trading Systems (LETS) and other currency types, e.g. "Ithaca HOUR".
+	 */
+	protected string $currency;
+
+	public function getCurrency():?string
+	{
+		return $this->currency??null;
+	}
+	public function setCurrency(string $currency):void
+	{
+		$this->currency = $currency;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/cutoffTimeTrait.php b/inc/managers/SEO/render/Traits/_Properties/cutoffTimeTrait.php
new file mode 100644
index 0000000..3b67404
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/cutoffTimeTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\DataType\Time;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait cutoffTimeTrait {
+	/**
+	 * @var Time Order cutoff time allows merchants to describe the time after which they will no longer process orders received on that day. For orders processed after cutoff time, one day gets added to the delivery time estimate. This property is expected to be most typically used via the ShippingRateSettings publication pattern. The time is indicated using the ISO-8601 Time format, e.g. "23:30:00-05:00" would represent 6:30 pm Eastern Standard Time (EST) which is 5 hours behind Coordinated Universal Time (UTC).
+	 */
+	protected Time $cutoffTime;
+
+	public function getCutoffTime():?Time
+	{
+		return $this->cutoffTime??null;
+	}
+	public function setCutoffTime(Time $cutoffTime):void
+	{
+		$this->cutoffTime = $cutoffTime;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/dateCreatedTrait.php b/inc/managers/SEO/render/Traits/_Properties/dateCreatedTrait.php
new file mode 100644
index 0000000..71f7b10
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/dateCreatedTrait.php
@@ -0,0 +1,31 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\DataType\Date;
+use JVBase\managers\SEO\render\DataType\DateTime;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait dateCreatedTrait {
+	/**
+	 * @var Date|DateTime The date on which the CreativeWork was created or the item was added to a DataFeed.
+	 */
+	protected Date|DateTime $dateCreated;
+
+	public function getDateCreated():Date|DateTime|null
+	{
+		return $this->dateCreated??null;
+	}
+
+	/**
+	 * @throws \DateMalformedStringException
+	 */
+	public function setDateCreated(Date|DateTime|string $dateCreated):void
+	{
+		if (is_string($dateCreated)) {
+			$dateCreated = new DateTime($dateCreated);
+		}
+		$this->dateCreated = $dateCreated;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/dateModifiedTrait.php b/inc/managers/SEO/render/Traits/_Properties/dateModifiedTrait.php
new file mode 100644
index 0000000..79c29c9
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/dateModifiedTrait.php
@@ -0,0 +1,27 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\DataType\Date;
+use JVBase\managers\SEO\render\DataType\DateTime;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait dateModifiedTrait {
+	/**
+	 * @var Date|DateTime The date on which the CreativeWork was most recently modified or when the item's entry was modified within a DataFeed.
+	 */
+	protected Date|DateTime $dateModified;
+
+	public function getDateModified():Date|DateTime|null
+	{
+		return $this->dateModified??null;
+	}
+	public function setDateModified(Date|DateTime|string $dateModified):void
+	{
+		if (is_string($dateModified)) {
+			$dateModified = new DateTime($dateModified);
+		}
+		$this->dateModified = $dateModified;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/datePublishedTrait.php b/inc/managers/SEO/render/Traits/_Properties/datePublishedTrait.php
new file mode 100644
index 0000000..2b26849
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/datePublishedTrait.php
@@ -0,0 +1,27 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\DataType\Date;
+use JVBase\managers\SEO\render\DataType\DateTime;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait datePublishedTrait {
+	/**
+	 * @var Date|DateTime Date of first publication or broadcast. For example the date a CreativeWork was broadcast or a Certification was issued.
+	 */
+	protected Date|DateTime $datePublished;
+
+	public function getDatePublished():Date|DateTime|null
+	{
+		return $this->datePublished??null;
+	}
+	public function setDatePublished(Date|DateTime|string $datePublished):void
+	{
+		if (is_string($datePublished)) {
+			$datePublished = new DateTime($datePublished);
+		}
+		$this->datePublished = $datePublished;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/dayOfWeekTrait.php b/inc/managers/SEO/render/Traits/_Properties/dayOfWeekTrait.php
new file mode 100644
index 0000000..73b6997
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/dayOfWeekTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\DayOfWeek;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait dayOfWeekTrait {
+	/**
+	 * @var DayOfWeek The day of the week from which these opening hours are valid
+	 */
+	protected DayOfWeek $dayOfWeek;
+
+	public function getDayOfWeek():?DayOfWeek
+	{
+		return $this->dayOfWeek??null;
+	}
+	public function setDayOfWeek(DayOfWeek $dayOfWeek):void
+	{
+		$this->dayOfWeek = $dayOfWeek;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/deathDateTrait.php b/inc/managers/SEO/render/Traits/_Properties/deathDateTrait.php
new file mode 100644
index 0000000..c2137ac
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/deathDateTrait.php
@@ -0,0 +1,31 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\DataType\Date;
+use JVBase\managers\SEO\render\DataType\DateTime;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait deathDateTrait {
+	/**
+	 * @var Date Date of death.
+	 */
+	protected Date $deathDate;
+
+	public function getDeathDate():?Date
+	{
+		return $this->deathDate??null;
+	}
+
+	/**
+	 * @throws \DateMalformedStringException
+	 */
+	public function setDeathDate(Date|string $deathDate):void
+	{
+		if (is_string($deathDate)) {
+			$deathDate = new Date($deathDate);
+		}
+		$this->deathDate = $deathDate;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/deathPlaceTrait.php b/inc/managers/SEO/render/Traits/_Properties/deathPlaceTrait.php
new file mode 100644
index 0000000..16e63d8
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/deathPlaceTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait deathPlaceTrait {
+	/**
+	 * @var Place The place where the person was born.
+	 */
+	protected Place $deathPlace;
+
+	public function getDeathPlace():?Place
+	{
+		return $this->deathPlace??null;
+	}
+	public function setDeathPlace(Place $deathPlace):void
+	{
+		$this->deathPlace = $deathPlace;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/deliveryLeadTimeTrait.php b/inc/managers/SEO/render/Traits/_Properties/deliveryLeadTimeTrait.php
new file mode 100644
index 0000000..4cd765e
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/deliveryLeadTimeTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait deliveryLeadTimeTrait {
+	/**
+	 * @var QuantitativeValue The typical delay between the receipt of the order and the goods either leaving the warehouse or being prepared for pickup, in case the delivery method is on site pickup.
+	 */
+	protected QuantitativeValue $deliveryLeadTime;
+
+	public function getDeliveryLeadTime():?QuantitativeValue
+	{
+		return $this->deliveryLeadTime??null;
+	}
+	public function setDeliveryLeadTime(QuantitativeValue $deliveryLeadTime):void
+	{
+		$this->deliveryLeadTime = $deliveryLeadTime;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/departmentTrait.php b/inc/managers/SEO/render/Traits/_Properties/departmentTrait.php
new file mode 100644
index 0000000..91b63e0
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/departmentTrait.php
@@ -0,0 +1,26 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait departmentTrait {
+	/**
+	 * @var Organization A relationship between an organization and a department of that organization, also described as an organization (allowing different urls, logos, opening hours). For example: a store with a pharmacy, or a bakery with a cafe.
+	 */
+	protected Organization $department;
+
+	public function getDepartment():?Organization
+	{
+		return $this->department??null;
+	}
+	public function setDepartment(Organization|array $department):void
+	{
+		if (is_array($department)){
+			$department = Organization::fromArray($department);
+		}
+		$this->department = $department;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/depthTrait.php b/inc/managers/SEO/render/Traits/_Properties/depthTrait.php
new file mode 100644
index 0000000..68ef399
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/depthTrait.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Quantity\Distance;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait depthTrait {
+	/**
+	 * @var Distance|QuantitativeValue The depth of the item.
+	 */
+	protected Distance|QuantitativeValue $depth;
+
+	public function getDepth():Distance|QuantitativeValue|null
+	{
+		return $this->depth??null;
+	}
+	public function setDepth(Distance|QuantitativeValue $depth):void
+	{
+		$this->depth = $depth;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/descriptionTrait.php b/inc/managers/SEO/render/Traits/_Properties/descriptionTrait.php
new file mode 100644
index 0000000..c756f0d
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/descriptionTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait descriptionTrait {
+	/**
+	 * @var string A description of the item
+	 */
+	protected string $description;
+
+	public function getDescription():?string
+	{
+		return $this->description??null;
+	}
+	public function setDescription(string $description):void
+	{
+		$this->description = $description;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/directorTrait.php b/inc/managers/SEO/render/Traits/_Properties/directorTrait.php
new file mode 100644
index 0000000..170852d
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/directorTrait.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Person\Person;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait directorTrait {
+	/**
+	 * @var Organization|Person A director of e.g. TV, radio, movie, video gaming etc. content, or of an event. Directors can be associated with individual items or with a series, episode, clip. Supersedes directors.
+	 */
+	protected Organization|Person $director;
+
+	public function getDirector():Organization|Person|null
+	{
+		return $this->director??null;
+	}
+	public function setDirector(Organization|Person $director):void
+	{
+		$this->director = $director;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/disambiguatingDescriptionTrait.php b/inc/managers/SEO/render/Traits/_Properties/disambiguatingDescriptionTrait.php
new file mode 100644
index 0000000..e0df631
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/disambiguatingDescriptionTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait disambiguatingDescriptionTrait {
+	/**
+	 * @var string A sub-property of description. A short description of the item used to disambiguate from other, similar items.
+	 */
+	protected string $disambiguatingDescription;
+
+	public function getDisambiguatingDescription():?string
+	{
+		return $this->disambiguatingDescription??null;
+	}
+	public function setDisambiguatingDescription(string $disambiguatingDescription):void
+	{
+		$this->disambiguatingDescription = $disambiguatingDescription;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/discussionUrlTrait.php b/inc/managers/SEO/render/Traits/_Properties/discussionUrlTrait.php
new file mode 100644
index 0000000..f49e400
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/discussionUrlTrait.php
@@ -0,0 +1,25 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait discussionUrlTrait {
+	/**
+	 * @var string A link to the page containing the comments of the CreativeWork.
+	 */
+	protected string $discussionUrl;
+
+	public function getDiscussionUrl():?string
+	{
+		return $this->discussionUrl??null;
+	}
+	public function setDiscussionUrl(string $discussionUrl):void
+	{
+		if (!filter_var($discussionUrl, FILTER_VALIDATE_URL)) {
+			error_log('[SEO] Discussion Url is intended to be a valid URL');
+			return;
+		}
+		$this->discussionUrl = $discussionUrl;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/displayLocationTrait.php b/inc/managers/SEO/render/Traits/_Properties/displayLocationTrait.php
new file mode 100644
index 0000000..2da007a
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/displayLocationTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait displayLocationTrait {
+	use arrayHelper;
+	/**
+	 * @var Place|array The location at which an item can be viewed or experienced in-person.
+	 */
+	protected Place|array $displayLocation;
+
+	public function getDisplayLocation():Place|array|null
+	{
+		return $this->displayLocation??null;
+	}
+	public function setDisplayLocation(Place|array $displayLocation):void
+	{
+		if (is_array($displayLocation)){
+			$displayLocation = $this->classArray('displayLocation', $displayLocation, 'JVBase\managers\SEO\render\Thing\Place\Place');
+		}
+		$this->displayLocation = $displayLocation;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/dissolutionDateTrait.php b/inc/managers/SEO/render/Traits/_Properties/dissolutionDateTrait.php
new file mode 100644
index 0000000..9b51d29
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/dissolutionDateTrait.php
@@ -0,0 +1,34 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\DataType\Date;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait dissolutionDateTrait {
+	/**
+	 * @var Date The date that this organization was dissolved.
+	 */
+	protected Date $dissolutionDate;
+
+	public function getDissolutionDate():?Date
+	{
+		return $this->dissolutionDate??null;
+	}
+	public function setDissolutionDate(Date|string $dissolutionDate):void
+	{
+		if (is_string($dissolutionDate)){
+			$dissolutionDate = new Date($dissolutionDate);
+		}
+		$this->dissolutionDate = $dissolutionDate;
+	}
+	public function getDissolutionDateFieldConfig():array
+	{
+		return [
+			'type'	=> 'date',
+			'label'	=> 'Dissolution Date',
+			'hint'	=> 'IMPORTANT: Do not fill this out, unless your business has actually closed.'
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/doorTimeTrait.php b/inc/managers/SEO/render/Traits/_Properties/doorTimeTrait.php
new file mode 100644
index 0000000..086ee80
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/doorTimeTrait.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\DataType\DateTime;
+use JVBase\managers\SEO\render\DataType\Time;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait doorTimeTrait {
+	/**
+	 * @var DateTime|Time The time admission will commence.
+	 */
+	protected DateTime|Time $doorTime;
+
+	public function getDoorTime():DateTime|Time|null
+	{
+		return $this->doorTime??null;
+	}
+	public function setDoorTime(DateTime|Time $doorTime):void
+	{
+		$this->doorTime = $doorTime;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/dunsTrait.php b/inc/managers/SEO/render/Traits/_Properties/dunsTrait.php
new file mode 100644
index 0000000..398b21b
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/dunsTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait dunsTrait {
+	/**
+	 * @var string The Dun & Bradstreet DUNS number for identifying an organization or business person.
+	 */
+	protected string $duns;
+
+	public function getDuns():?string
+	{
+		return $this->duns??null;
+	}
+	public function setDuns(string $duns):void
+	{
+		$this->duns = $duns;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/durationOfWarrantyTrait.php b/inc/managers/SEO/render/Traits/_Properties/durationOfWarrantyTrait.php
new file mode 100644
index 0000000..9e5f974
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/durationOfWarrantyTrait.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Quantity\DurationOfWarranty;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait durationOfWarrantyTrait {
+	/**
+	 * @var QuantitativeValue The duration of the warranty promise. Common unitCode values are ANN for year, MON for months, or DAY for days.
+	 */
+	protected QuantitativeValue $durationOfWarranty;
+
+	public function getDurationOfWarranty():?QuantitativeValue
+	{
+		return $this->durationOfWarranty??null;
+	}
+	public function setDurationOfWarranty(QuantitativeValue $durationOfWarranty):void
+	{
+		$this->durationOfWarranty = $durationOfWarranty;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/durationTrait.php b/inc/managers/SEO/render/Traits/_Properties/durationTrait.php
new file mode 100644
index 0000000..9165e8d
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/durationTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Quantity\Duration;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait durationTrait {
+	/**
+	 * @var Duration The duration of the item (movie, audio recording, event, etc.) in ISO 8601 duration format.
+	 */
+	protected Duration $duration;
+
+	public function getDuration():?Duration
+	{
+		return $this->duration??null;
+	}
+	public function setDuration(Duration $duration):void
+	{
+		$this->duration = $duration;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/editorTrait.php b/inc/managers/SEO/render/Traits/_Properties/editorTrait.php
new file mode 100644
index 0000000..b22baa5
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/editorTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Person\Person;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait editorTrait {
+	/**
+	 * @var Person Specifies the Person who edited the CreativeWork.
+	 */
+	protected Person $editor;
+
+	public function getEditor():?Person
+	{
+		return $this->editor??null;
+	}
+	public function setEditor(Person $editor):void
+	{
+		$this->editor = $editor;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/educationRequirementsTrait.php b/inc/managers/SEO/render/Traits/_Properties/educationRequirementsTrait.php
new file mode 100644
index 0000000..26b45cf
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/educationRequirementsTrait.php
@@ -0,0 +1,31 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\EducationalOccupationalCredential;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait educationRequirementsTrait {
+	use arrayHelper;
+	/**
+	 * @var EducationalOccupationalCredential|string|array Educational background needed for the position or Occupation.
+	 */
+	protected EducationalOccupationalCredential|string|array $educationRequirements;
+
+	public function getEducationRequirements():EducationalOccupationalCredential|string|array|null
+	{
+		return $this->educationRequirements??null;
+	}
+	public function setEducationRequirements(EducationalOccupationalCredential|string|array $educationRequirements):void
+	{
+		if (is_array($educationRequirements)) {
+			$educationRequirements = $this->mixedArray('educationRequirements', $educationRequirements, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\CreativeWork\EducationalOccupationalCredential',
+			]);
+		}
+		$this->educationRequirements = $educationRequirements;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/elevationTrait.php b/inc/managers/SEO/render/Traits/_Properties/elevationTrait.php
new file mode 100644
index 0000000..9d6643a
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/elevationTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait elevationTrait {
+	/**
+	 * @var int|float|string The elevation of a location (WGS 84). Values may be of the form 'NUMBER UNIT_OF_MEASUREMENT' (e.g., '1,000 m', '3,200 ft') while numbers alone should be assumed to be a value in meters.
+	 */
+	protected int|float|string $elevation;
+
+	public function getElevation():int|float|string|null
+	{
+		return $this->elevation??null;
+	}
+	public function setElevation(int|float|string $elevation):void
+	{
+		$this->elevation = $elevation;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/eligibleCustomerTypeTrait.php b/inc/managers/SEO/render/Traits/_Properties/eligibleCustomerTypeTrait.php
new file mode 100644
index 0000000..8f89f8f
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/eligibleCustomerTypeTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\BusinessEntityType;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait eligibleCustomerTypeTrait {
+	use arrayHelper;
+	/**
+	 * @var BusinessEntityType|array The type(s) of customers for which the given offer is valid.
+	 */
+	protected BusinessEntityType|array $eligibleCustomerType;
+
+	public function getEligibleCustomerType():BusinessEntityType|array|null
+	{
+		return $this->eligibleCustomerType??null;
+	}
+	public function setEligibleCustomerType(BusinessEntityType|array $eligibleCustomerType):void
+	{
+		if (is_array($eligibleCustomerType)) {
+			$eligibleCustomerType = $this->classArray('eligibleCustomerType', $eligibleCustomerType, 'JVBase\managers\SEO\render\Thing\Intangible\Enumeration\BusinessEntityType');
+		}
+		$this->eligibleCustomerType = $eligibleCustomerType;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/eligibleDurationTrait.php b/inc/managers/SEO/render/Traits/_Properties/eligibleDurationTrait.php
new file mode 100644
index 0000000..2fd8482
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/eligibleDurationTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait eligibleDurationTrait {
+	/**
+	 * @var QuantitativeValue The duration for which the given offer is valid.
+	 */
+	protected QuantitativeValue $eligibleDuration;
+
+	public function getEligibleDuration():?QuantitativeValue
+	{
+		return $this->eligibleDuration??null;
+	}
+	public function setEligibleDuration(QuantitativeValue $eligibleDuration):void
+	{
+		$this->eligibleDuration = $eligibleDuration;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/eligibleQuantityTrait.php b/inc/managers/SEO/render/Traits/_Properties/eligibleQuantityTrait.php
new file mode 100644
index 0000000..828b6f7
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/eligibleQuantityTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait eligibleQuantityTrait {
+	/**
+	 * @var QuantitativeValue The duration for which the given offer is valid.
+	 */
+	protected QuantitativeValue $eligibleQuantity;
+
+	public function getEligibleQuantity():?QuantitativeValue
+	{
+		return $this->eligibleQuantity??null;
+	}
+	public function setEligibleQuantity(QuantitativeValue $eligibleQuantity):void
+	{
+		$this->eligibleQuantity = $eligibleQuantity;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/eligibleRegionTrait.php b/inc/managers/SEO/render/Traits/_Properties/eligibleRegionTrait.php
new file mode 100644
index 0000000..06c0d19
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/eligibleRegionTrait.php
@@ -0,0 +1,33 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\GeoShape;
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait eligibleRegionTrait {
+	use arrayHelper;
+	/**
+	 * @var GeoShape|Place|string|array The ISO 3166-1 (ISO 3166-1 alpha-2) or ISO 3166-2 code, the place, or the GeoShape for the geo-political region(s) for which the offer or delivery charge specification is valid.
+	 */
+	protected GeoShape|Place|string|array $eligibleRegion;
+
+	public function getEligibleRegion():GeoShape|Place|string|array|null
+	{
+		return $this->eligibleRegion??null;
+	}
+	public function setEligibleRegion(GeoShape|Place|string|array $eligibleRegion):void
+	{
+		if (is_array($eligibleRegion)) {
+			$eligibleRegion = $this->mixedArray('eligibleRegion', $eligibleRegion, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\GeoShape',
+				'JVBase\managers\SEO\render\Thing\Place\Place'
+			]);
+		}
+		$this->eligibleRegion = $eligibleRegion;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/eligibleTransactionVolumeTrait.php b/inc/managers/SEO/render/Traits/_Properties/eligibleTransactionVolumeTrait.php
new file mode 100644
index 0000000..e90a02d
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/eligibleTransactionVolumeTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\PriceSpecification;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait eligibleTransactionVolumeTrait {
+	/**
+	 * @var PriceSpecification The transaction volume, in a monetary unit, for which the offer or price specification is valid, e.g. for indicating a minimal purchasing volume, to express free shipping above a certain order volume, or to limit the acceptance of credit cards to purchases to a certain minimal amount.
+	 */
+	protected PriceSpecification $eligibleTransactionVolume;
+
+	public function getEligibleTransactionVolume():?PriceSpecification
+	{
+		return $this->eligibleTransactionVolume??null;
+	}
+	public function setEligibleTransactionVolume(PriceSpecification $eligibleTransactionVolume):void
+	{
+		$this->eligibleTransactionVolume = $eligibleTransactionVolume;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/emailTrait.php b/inc/managers/SEO/render/Traits/_Properties/emailTrait.php
new file mode 100644
index 0000000..7927eaa
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/emailTrait.php
@@ -0,0 +1,32 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait emailTrait {
+	/**
+	 * @var string Email address
+	 */
+	protected string $email;
+
+	public function getEmail():?string
+	{
+		return $this->email??null;
+	}
+	public function setEmail(string $email):void
+	{
+		if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
+			error_log('[SEO] Email is not valid');
+			return;
+		}
+		$this->email = $email;
+	}
+	public function getEmailFieldConfig():array
+	{
+		return [
+			'type'	=> 'email',
+			'label'	=> 'Email',
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/embedURLTrait.php b/inc/managers/SEO/render/Traits/_Properties/embedURLTrait.php
new file mode 100644
index 0000000..ba7ad9c
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/embedURLTrait.php
@@ -0,0 +1,25 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait embedURLTrait {
+	/**
+	 * @var string A URL pointing to a player for a specific video. In general, this is the information in the src element of an embed tag and should not be the same as the content of the loc tag.
+	 */
+	protected string $embedURL;
+
+	public function getEmbedURL():?string
+	{
+		return $this->embedURL??null;
+	}
+	public function setEmbedURL(string $embedURL):void
+	{
+		if (!filter_var($embedURL, FILTER_VALIDATE_URL)) {
+			error_log('[SEO]embedURL is intended to be a valid URL: '.$embedURL);
+			return;
+		}
+		$this->embedURL = $embedURL;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/embeddedTextCaptionTrait.php b/inc/managers/SEO/render/Traits/_Properties/embeddedTextCaptionTrait.php
new file mode 100644
index 0000000..3cb8677
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/embeddedTextCaptionTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait embeddedTextCaptionTrait {
+	/**
+	 * @var string Represents textual captioning from a MediaObject, e.g. text of a 'meme'.
+	 */
+	protected string $embeddedTextCaption;
+
+	public function getEmbeddedTextCaption():?string
+	{
+		return $this->embeddedTextCaption??null;
+	}
+	public function setEmbeddedTextCaption(string $embeddedTextCaption):void
+	{
+		$this->embeddedTextCaption = $embeddedTextCaption;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/employeeTrait.php b/inc/managers/SEO/render/Traits/_Properties/employeeTrait.php
new file mode 100644
index 0000000..9941a2e
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/employeeTrait.php
@@ -0,0 +1,33 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait employeeTrait {
+	use arrayHelper;
+	/**
+	 * @var Person Someone working for this organization. Supersedes employees.
+	 */
+	protected Person $employee;
+
+	public function getEmployee():?Person
+	{
+		return $this->employee??null;
+	}
+
+	/**
+	 * @param Person|array $employee a Person or an array of Person objects}
+	 * @return void
+	 */
+	public function setEmployee(Person|array $employee):void
+	{
+		if (is_array($employee)) {
+			$employee = $this->classArray('employee', $employee, 'JVBase\managers\SEO\render\Thing\Person\Person');
+		}
+		$this->employee = $employee;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/encodingFormatTrait.php b/inc/managers/SEO/render/Traits/_Properties/encodingFormatTrait.php
new file mode 100644
index 0000000..61fc5d9
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/encodingFormatTrait.php
@@ -0,0 +1,25 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait encodingFormatTrait {
+	/**
+	 * @var string Media type typically expressed using a MIME format (see IANA site and MDN reference), e.g. application/zip for a SoftwareApplication binary, audio/mpeg for .mp3 etc.
+	 *
+	 * In cases where a CreativeWork has several media type representations, encoding can be used to indicate each MediaObject alongside particular encodingFormat information.
+	 *
+	 * Unregistered or niche encoding and file formats can be indicated instead via the most appropriate URL, e.g. defining Web page or a Wikipedia/Wikidata entry. Supersedes fileFormat.
+	 */
+	protected string $encodingFormat;
+
+	public function getEncodingFormat():?string
+	{
+		return $this->encodingFormat??null;
+	}
+	public function setEncodingFormat(string $encodingFormat):void
+	{
+		$this->encodingFormat = $encodingFormat;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/encodingTrait.php b/inc/managers/SEO/render/Traits/_Properties/encodingTrait.php
new file mode 100644
index 0000000..1ca6e46
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/encodingTrait.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\MediaObject\MediaObject;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait encodingTrait {
+	/**
+	 * @var MediaObject A media object that encodes this CreativeWork. This property is a synonym for associatedMedia. Supersedes encodings.
+	 * Inverse property: encodesCreativeWork
+	 */
+	protected MediaObject $encoding;
+
+	public function getEncoding():?MediaObject
+	{
+		return $this->encoding??null;
+	}
+	public function setEncoding(MediaObject $encoding):void
+	{
+		$this->encoding = $encoding;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/encodingTypeTrait.php b/inc/managers/SEO/render/Traits/_Properties/encodingTypeTrait.php
new file mode 100644
index 0000000..f488723
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/encodingTypeTrait.php
@@ -0,0 +1,27 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait encodingTypeTrait {
+	use arrayHelper;
+	/**
+	 * @var string|array The supported encoding type(s) for an EntryPoint request.
+	 */
+	protected string|array $encodingType;
+
+	public function getEncodingType():string|array|null
+	{
+		return $this->encodingType??null;
+	}
+	public function setEncodingType(string|array $encodingType):void
+	{
+		if (is_array($encodingType)) {
+			$encodingType = $this->stringArray('encodingType', $encodingType);
+		}
+		$this->encodingType = $encodingType;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/endDateTrait.php b/inc/managers/SEO/render/Traits/_Properties/endDateTrait.php
new file mode 100644
index 0000000..1cf235c
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/endDateTrait.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\DataType\Date;
+use JVBase\managers\SEO\render\DataType\DateTime;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait endDateTrait {
+	/**
+	 * @var Date|DateTime The end date and time of the item (in ISO 8601 date format).
+	 */
+	protected Date|DateTime $endDate;
+
+	public function getEndDate():Date|DateTime|null
+	{
+		return $this->endDate??null;
+	}
+	public function setEndDate(Date|DateTime $endDate):void
+	{
+		$this->endDate = $endDate;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/endOffsetTrait.php b/inc/managers/SEO/render/Traits/_Properties/endOffsetTrait.php
new file mode 100644
index 0000000..1d81d0b
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/endOffsetTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait endOffsetTrait {
+	/**
+	 * @var int|float The end time of the clip expressed as the number of seconds from the beginning of the work.
+	 */
+	protected int|float $endOffset;
+
+	public function getEndOffset():int|float|null
+	{
+		return $this->endOffset??null;
+	}
+	public function setEndOffset(int|float $endOffset):void
+	{
+		$this->endOffset = $endOffset;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/endTimeTrait.php b/inc/managers/SEO/render/Traits/_Properties/endTimeTrait.php
new file mode 100644
index 0000000..e6e0d67
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/endTimeTrait.php
@@ -0,0 +1,26 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\DataType\DateTime;
+use JVBase\managers\SEO\render\DataType\Time;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait endTimeTrait {
+	/**
+	 * @var Time|DateTime The endTime of something. For a reserved event or service (e.g. FoodEstablishmentReservation), the time that it is expected to end. For actions that span a period of time, when the action was performed. E.g. John wrote a book from January to December. For media, including audio and video, it's the time offset of the end of a clip within a larger file.
+	 *
+	 * Note that Event uses startDate/endDate instead of startTime/endTime, even when describing dates with times. This situation may be clarified in future revisions.
+	 */
+	protected Time|DateTime $endTime;
+
+	public function getEndTime():Time|DateTime|null
+	{
+		return $this->endTime??null;
+	}
+	public function setEndTime(Time|DateTime $endTime):void
+	{
+		$this->endTime = $endTime;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/errorTrait.php b/inc/managers/SEO/render/Traits/_Properties/errorTrait.php
new file mode 100644
index 0000000..ff013fb
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/errorTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait errorTrait {
+	/**
+	 * @var Thing For failed actions, more information on the cause of the failure. Consider using the Error type.
+	 */
+	protected Thing $error;
+
+	public function getError():?Thing
+	{
+		return $this->error??null;
+	}
+	public function setError(Thing $error):void
+	{
+		$this->error = $error;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/estimateCostTrait.php b/inc/managers/SEO/render/Traits/_Properties/estimateCostTrait.php
new file mode 100644
index 0000000..41b760e
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/estimateCostTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\MonetaryAmount;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait estimateCostTrait {
+	/**
+	 * @var MonetaryAmount|string The estimated cost of the supply or supplies consumed when performing instructions.
+	 */
+	protected MonetaryAmount|string $estimateCost;
+
+	public function getEstimateCost():MonetaryAmount|string|null
+	{
+		return $this->estimateCost??null;
+	}
+	public function setEstimateCost(MonetaryAmount|string $estimateCost):void
+	{
+		$this->estimateCost = $estimateCost;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/ethicsPolicyTrait.php b/inc/managers/SEO/render/Traits/_Properties/ethicsPolicyTrait.php
new file mode 100644
index 0000000..4da5d87
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/ethicsPolicyTrait.php
@@ -0,0 +1,29 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait ethicsPolicyTrait {
+	/**
+	 * @var CreativeWork|string Statement about ethics policy, e.g. of a NewsMediaOrganization regarding journalistic and publishing practices, or of a Restaurant, a page describing food source policies. In the case of a NewsMediaOrganization, an ethicsPolicy is typically a statement describing the personal, organizational, and corporate standards of behavior expected by the organization.
+	 */
+	protected CreativeWork|string $ethicsPolicy;
+
+	public function getEthicsPolicy():CreativeWork|string|null
+	{
+		return $this->ethicsPolicy??null;
+	}
+	public function setEthicsPolicy(CreativeWork|string $ethicsPolicy):void
+	{
+		if (is_string($ethicsPolicy)) {
+			if(!filter_var($ethicsPolicy, FILTER_VALIDATE_URL)) {
+				error_log('[SEO] ethicsPolicy should be a valid URL');
+				return;
+			}
+		}
+		$this->ethicsPolicy = $ethicsPolicy;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/eventTrait.php b/inc/managers/SEO/render/Traits/_Properties/eventTrait.php
new file mode 100644
index 0000000..df1799c
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/eventTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Event\Event;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait eventTrait {
+	use arrayHelper;
+	/**
+	 * @var Event|array Upcoming or past event associated with this place, organization, or action. Supersedes events.
+	 */
+	protected Event|array $event;
+
+	public function getEvent():Event|array|null
+	{
+		return $this->event??null;
+	}
+	public function setEvent(Event|array $event):void
+	{
+		if(is_array($event)){
+			$event = $this->classArray('event', $event, 'JVBase\managers\SEO\render\Thing\Event\Event');
+		}
+		$this->event = $event;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/exampleOfWorkTrait.php b/inc/managers/SEO/render/Traits/_Properties/exampleOfWorkTrait.php
new file mode 100644
index 0000000..9f2e438
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/exampleOfWorkTrait.php
@@ -0,0 +1,29 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait exampleOfWorkTrait {
+	use arrayHelper;
+	/**
+	 * @var CreativeWork|array A creative work that this work is an example/instance/realization/derivation of.
+	 * Inverse property: workExample
+	 */
+	protected CreativeWork|array $exampleOfWork;
+
+	public function getExampleOfWork():CreativeWork|array|null
+	{
+		return $this->exampleOfWork??null;
+	}
+	public function setExampleOfWork(CreativeWork|array $exampleOfWork):void
+	{
+		if (is_array($exampleOfWork)) {
+			$exampleOfWork = $this->classArray('exampleOfWork', $exampleOfWork, 'JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork');
+		}
+		$this->exampleOfWork = $exampleOfWork;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/exceptDateTrait.php b/inc/managers/SEO/render/Traits/_Properties/exceptDateTrait.php
new file mode 100644
index 0000000..7e5d11b
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/exceptDateTrait.php
@@ -0,0 +1,32 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\DataType\Date;
+use JVBase\managers\SEO\render\DataType\DateTime;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait exceptDateTrait {
+	use arrayHelper;
+	/**
+	 * @var Date|DateTime|array The end date and time of the item (in ISO 8601 date format).
+	 */
+	protected Date|DateTime|array $exceptDate;
+
+	public function getExceptDate():Date|DateTime|array|null
+	{
+		return $this->exceptDate??null;
+	}
+	public function setExceptDate(Date|DateTime|array $exceptDate):void
+	{
+		if (is_array($exceptDate)) {
+			$exceptDate = $this->mixedArray('exceptDate', $exceptDate, [
+				'JVBase\managers\SEO\render\DataType\Date',
+				'JVBase\managers\SEO\render\DataType\DateTime'
+			]);
+		}
+		$this->exceptDate = $exceptDate;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/exifDataTrait.php b/inc/managers/SEO/render/Traits/_Properties/exifDataTrait.php
new file mode 100644
index 0000000..5e13190
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/exifDataTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\PropertyValue;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait exifDataTrait {
+	/**
+	 * @var string|PropertyValue exif data for this object
+	 */
+	protected string|PropertyValue $exifData;
+
+	public function getExifData():string|PropertyValue|null
+	{
+		return $this->exifData??null;
+	}
+	public function setExifData(string|PropertyValue $exifData):void
+	{
+		$this->exifData = $exifData;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/experienceRequirementsTrait.php b/inc/managers/SEO/render/Traits/_Properties/experienceRequirementsTrait.php
new file mode 100644
index 0000000..bd7b9c5
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/experienceRequirementsTrait.php
@@ -0,0 +1,31 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\OccupationalExperienceRequirements;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait experienceRequirementsTrait {
+	use arrayHelper;
+	/**
+	 * @var OccupationalExperienceRequirements|string|array ExperienceRequirements of skills and experience needed for the position or Occupation.
+	 */
+	protected OccupationalExperienceRequirements|string|array $experienceRequirements;
+
+	public function getExperienceRequirements():OccupationalExperienceRequirements|string|array|null
+	{
+		return $this->experienceRequirements??null;
+	}
+	public function setExperienceRequirements(OccupationalExperienceRequirements|string|array $experienceRequirements):void
+	{
+		if (is_array($experienceRequirements)) {
+			$experienceRequirements = $this->mixedArray('experienceRequirements', $experienceRequirements, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\Intangible\OccupationalExperienceRequirements',
+			]);
+		}
+		$this->experienceRequirements = $experienceRequirements;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/expiresTrait.php b/inc/managers/SEO/render/Traits/_Properties/expiresTrait.php
new file mode 100644
index 0000000..69783dc
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/expiresTrait.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\DataType\Date;
+use JVBase\managers\SEO\render\DataType\DateTime;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait expiresTrait {
+	/**
+	 * @var Date|DateTime Date the content expires and is no longer useful or available. For example a VideoObject or NewsArticle whose availability or relevance is time-limited, a ClaimReview fact check whose publisher wants to indicate that it may no longer be relevant (or helpful to highlight) after some date, or a Certification the validity has expired.
+	 */
+	protected Date|DateTime $expires;
+
+	public function getExpires():Date|DateTime|null
+	{
+		return $this->expires??null;
+	}
+	public function setExpires(Date|DateTime $expires):void
+	{
+		$this->expires = $expires;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/extendedAddressTrait.php b/inc/managers/SEO/render/Traits/_Properties/extendedAddressTrait.php
new file mode 100644
index 0000000..f1781b5
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/extendedAddressTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait extendedAddressTrait {
+	/**
+	 * @var string An address extension such as an apartment number, C/O or alternative name.
+	 */
+	protected string $extendedAddress;
+
+	public function getExtendedAddress():?string
+	{
+		return $this->extendedAddress??null;
+	}
+	public function setExtendedAddress(string $extendedAddress):void
+	{
+		$this->extendedAddress = $extendedAddress;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/familyNameTrait.php b/inc/managers/SEO/render/Traits/_Properties/familyNameTrait.php
new file mode 100644
index 0000000..5bed42e
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/familyNameTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait familyNameTrait {
+	/**
+	 * @var string Family name. In the U.S., the last name of a Person.
+	 */
+	protected string $familyName;
+
+	public function getFamilyName():?string
+	{
+		return $this->familyName??null;
+	}
+	public function setFamilyName(string $familyName):void
+	{
+		$this->familyName = $familyName;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/fatContentTrait.php b/inc/managers/SEO/render/Traits/_Properties/fatContentTrait.php
new file mode 100644
index 0000000..789bf89
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/fatContentTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait fatContentTrait {
+	/**
+	 * @var int|float The number of fatContent.
+	 */
+	protected int|float $fatContent;
+
+	public function getFatContent():int|float|null
+	{
+		return $this->fatContent??null;
+	}
+	public function setFatContent(int|float $fatContent):void
+	{
+		$this->fatContent = $fatContent;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/faxNumberTrait.php b/inc/managers/SEO/render/Traits/_Properties/faxNumberTrait.php
new file mode 100644
index 0000000..8718a1d
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/faxNumberTrait.php
@@ -0,0 +1,27 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\meta\Sanitizer;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait faxNumberTrait {
+	/**
+	 * @var string The fax number
+	 */
+	protected string $faxNumber;
+
+	public function getFaxNumber():?string
+	{
+		return $this->faxNumber??null;
+	}
+	public function setFaxNumber(string $faxNumber):void
+	{
+		if (Sanitizer::sanitizePhone($faxNumber) === '') {
+			error_log('[SEO] FaxNumber is not valid');
+			return;
+		}
+		$this->faxNumber = $faxNumber;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/fiberContentTrait.php b/inc/managers/SEO/render/Traits/_Properties/fiberContentTrait.php
new file mode 100644
index 0000000..282af19
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/fiberContentTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait fiberContentTrait {
+	/**
+	 * @var int|float The number of grams of fiber
+	 */
+	protected int|float $fiberContent;
+
+	public function getFiberContent():int|float|null
+	{
+		return $this->fiberContent??null;
+	}
+	public function setFiberContent(int|float $fiberContent):void
+	{
+		$this->fiberContent = $fiberContent;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/followsTrait.php b/inc/managers/SEO/render/Traits/_Properties/followsTrait.php
new file mode 100644
index 0000000..e646694
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/followsTrait.php
@@ -0,0 +1,31 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait followsTrait {
+	use arrayHelper;
+	/**
+	 * @var Person|string|array The most generic uni-directional social relation.
+	 */
+	protected Person|string|array $follows;
+
+	public function getFollows():Person|string|array|null
+	{
+		return $this->follows??null;
+	}
+	public function setFollows(Person|string|array $follows):void
+	{
+		if (is_array($follows)) {
+			$follows = $this->mixedArray('follows', $follows, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\Person\Person'
+			]);
+		}
+		$this->follows = $follows;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/founderTrait.php b/inc/managers/SEO/render/Traits/_Properties/founderTrait.php
new file mode 100644
index 0000000..d42fc6e
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/founderTrait.php
@@ -0,0 +1,29 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait founderTrait {
+	use arrayHelper;
+	/**
+	 * @var Organization|Person|array A person or organization who founded this organization. Supersedes founders.
+	 */
+	protected Organization|Person|array $founder;
+
+	public function getFounder():Organization|Person|array|null
+	{
+		return $this->founder??null;
+	}
+	public function setFounder(Organization|Person|array $founder):void
+	{
+		if (is_array($founder)) {
+			$founder = $this->mixedArray('founder', $founder, ['JVBase\managers\SEO\render\Thing\Organization\Organization', 'JVBase\managers\SEO\render\Thing\Person\Person']);
+		}
+		$this->founder = $founder;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/foundingDateTrait.php b/inc/managers/SEO/render/Traits/_Properties/foundingDateTrait.php
new file mode 100644
index 0000000..f5dd72c
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/foundingDateTrait.php
@@ -0,0 +1,37 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\DataType\Date;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait foundingDateTrait {
+	/**
+	 * @var Date A foundingDate of the item
+	 */
+	protected Date $foundingDate;
+
+	public function getFoundingDate():?Date
+	{
+		return $this->foundingDate??null;
+	}
+
+	/**
+	 * @throws \DateMalformedStringException
+	 */
+	public function setFoundingDate(Date|string $foundingDate):void
+	{
+		if (is_string($foundingDate)) {
+			$foundingDate = new Date($foundingDate);
+		}
+		$this->foundingDate = $foundingDate;
+	}
+	public function getFoundingDateFieldConfig():array
+	{
+		return [
+			'type'		=> 'date',
+			'label'		=> 'Founding Date'
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/foundingLocationTrait.php b/inc/managers/SEO/render/Traits/_Properties/foundingLocationTrait.php
new file mode 100644
index 0000000..bd44ce1
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/foundingLocationTrait.php
@@ -0,0 +1,42 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait foundingLocationTrait {
+	/**
+	 * @var Place The place where the Organization was founded.
+	 */
+	protected Place $foundingLocation;
+
+	public function getFoundingLocation():?Place
+	{
+		return $this->foundingLocation??null;
+	}
+	public function setFoundingLocation(Place $foundingLocation):void
+	{
+		$this->foundingLocation = $foundingLocation;
+	}
+
+	public function getFoundingLocationFieldConfig():array
+	{
+		return [
+			'type'		=> 'group',
+			'label'		=> 'Founding Location',
+			'fields'	=> [
+				'name'	=> [
+					'type' 	=> 'text',
+					'label'	=> 'City Name'
+				],
+				'sameAs'	=> [
+					'type'	=> 'url',
+					'label'	=> 'Same as',
+					'hint'	=> 'Wikipedia page of neighbourhood/city/province/country'
+				]
+			]
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/fundedItemTrait.php b/inc/managers/SEO/render/Traits/_Properties/fundedItemTrait.php
new file mode 100644
index 0000000..21344cf
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/fundedItemTrait.php
@@ -0,0 +1,39 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+use JVBase\managers\SEO\render\Thing\Event\Event;
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Thing\Product\Product;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait fundedItemTrait {
+	use arrayHelper;
+	/**
+	 * @var CreativeWork|Event|Organization|Person|Product|array Indicates something directly or indirectly funded or sponsored through a Grant. See also ownershipFundingInfo.
+	 * Inverse property: funding
+	 */
+	protected CreativeWork|Event|Organization|Person|Product|array $fundedItem;
+
+	public function getFundedItem():CreativeWork|Event|Organization|Person|Product|array|null
+	{
+		return $this->fundedItem??null;
+	}
+	public function setFundedItem(CreativeWork|Event|Organization|Person|Product|array $fundedItem):void
+	{
+		if (is_array($fundedItem)){
+			$fundedItem = $this->mixedArray('fundedItem', $fundedItem, [
+				'JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork',
+				'JVBase\managers\SEO\render\Thing\Event\Event',
+				'JVBase\managers\SEO\render\Thing\Organization\Organization',
+				'JVBase\managers\SEO\render\Thing\Person\Person',
+				'JVBase\managers\SEO\render\Thing\Product\Product'
+			]);
+		}
+		$this->fundedItem = $fundedItem;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/funderTrait.php b/inc/managers/SEO/render/Traits/_Properties/funderTrait.php
new file mode 100644
index 0000000..b3ac3b0
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/funderTrait.php
@@ -0,0 +1,29 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait funderTrait {
+	use arrayHelper;
+	/**
+	 * @var Organization|Person|array A person or organization that supports (sponsors) something through some kind of financial contribution.
+	 */
+	protected Organization|Person|array $funder;
+
+	public function getFunder():Organization|Person|array|null
+	{
+		return $this->funder??null;
+	}
+	public function setFunder(Organization|Person|array $funder):void
+	{
+		if (is_array($funder)){
+			$funder = $this->mixedArray('funder', $funder, ['JVBase\managers\SEO\render\Thing\Organization\Organization', 'JVBase\managers\SEO\render\Thing\Person\Person']);
+		}
+		$this->funder = $funder;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/fundingTrait.php b/inc/managers/SEO/render/Traits/_Properties/fundingTrait.php
new file mode 100644
index 0000000..cfb613a
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/fundingTrait.php
@@ -0,0 +1,29 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Grant;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait fundingTrait {
+	use arrayHelper;
+	/**
+	 * @var Grant|array A Grant that directly or indirectly provide funding or sponsorship for this item. See also ownershipFundingInfo.
+	 * Inverse property: fundedItem
+	 */
+	protected Grant|array $funding;
+
+	public function getFunding():Grant|array|null
+	{
+		return $this->funding??null;
+	}
+	public function setFunding(Grant|array $funding):void
+	{
+		if (is_array($funding)){
+			$funding = $this->classArray('funding', $funding, 'JVBase\managers\SEO\render\Thing\Intangible\Grant');
+		}
+		$this->funding = $funding;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/genreTrait.php b/inc/managers/SEO/render/Traits/_Properties/genreTrait.php
new file mode 100644
index 0000000..3cf3e64
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/genreTrait.php
@@ -0,0 +1,31 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\DefinedTerm;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait genreTrait {
+	use arrayHelper;
+	/**
+	 * @var DefinedTerm|string|array Genre of the creative work, broadcast channel or group.
+	 */
+	protected DefinedTerm|string|array $genre;
+
+	public function getGenre():DefinedTerm|string|array|null
+	{
+		return $this->genre??null;
+	}
+	public function setGenre(DefinedTerm|string|array $genre):void
+	{
+		if (is_array($genre)) {
+			$genre = $this->mixedArray('genre', $genre, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\Intangible\DefinedTerm'
+			]);
+		}
+		$this->genre = $genre;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/geoTrait.php b/inc/managers/SEO/render/Traits/_Properties/geoTrait.php
new file mode 100644
index 0000000..c82429f
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/geoTrait.php
@@ -0,0 +1,44 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\GeoCoordinates;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\GeoShape;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait geoTrait {
+	/**
+	 * @var GeoCoordinates|GeoShape The geo coordinates of the place.
+	 */
+	protected GeoCoordinates|GeoShape $geo;
+
+	public function getGeo():GeoCoordinates|GeoShape|null
+	{
+		return $this->geo??null;
+	}
+	public function setGeo(GeoCoordinates|GeoShape $geo):void
+	{
+		$this->geo = $geo;
+	}
+	public function getGeoFieldConfig():array
+	{
+		return [
+			'type'	=> 'group',
+			'wrap'	=> 'details',
+			'label'	=> 'Coordinates',
+			'fields'	=> [
+				'latitude'	=> [
+					'type'	=> 'text',
+					'label'	=> 'Latitude',
+					'hint'	=> 'The latitude of a location. For example 37.42242'
+				],
+				'longitude'	=> [
+					'type'	=> 'text',
+					'label'	=> 'Longitude',
+					'hint'	=> 'The longitude of a location. For example -122.08585'
+				]
+			]
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/geographicAreaTrait.php b/inc/managers/SEO/render/Traits/_Properties/geographicAreaTrait.php
new file mode 100644
index 0000000..2b7eca1
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/geographicAreaTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Place\AdministrativeArea\AdministrativeArea;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait geographicAreaTrait {
+	/**
+	 * @var AdministrativeArea The geographic area associated with the audience.
+	 */
+	protected AdministrativeArea $geographicArea;
+
+	public function getGeographicArea():?AdministrativeArea
+	{
+		return $this->geographicArea??null;
+	}
+	public function setGeographicArea(AdministrativeArea $geographicArea):void
+	{
+		$this->geographicArea = $geographicArea;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/givenNameTrait.php b/inc/managers/SEO/render/Traits/_Properties/givenNameTrait.php
new file mode 100644
index 0000000..7d5fb17
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/givenNameTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait givenNameTrait {
+	/**
+	 * @var string Given name. In the U.S., the first name of a Person.
+	 */
+	protected string $givenName;
+
+	public function getGivenName():?string
+	{
+		return $this->givenName??null;
+	}
+	public function setGivenName(string $givenName):void
+	{
+		$this->givenName = $givenName;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/globalLocationNumberTrait.php b/inc/managers/SEO/render/Traits/_Properties/globalLocationNumberTrait.php
new file mode 100644
index 0000000..fc9ff99
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/globalLocationNumberTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait globalLocationNumberTrait {
+	/**
+	 * @var string A globalLocationNumber of the item
+	 */
+	protected string $globalLocationNumber;
+
+	public function getGlobalLocationNumber():?string
+	{
+		return $this->globalLocationNumber??null;
+	}
+	public function setGlobalLocationNumber(string $globalLocationNumber):void
+	{
+		$this->globalLocationNumber = $globalLocationNumber;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/gtinTrait.php b/inc/managers/SEO/render/Traits/_Properties/gtinTrait.php
new file mode 100644
index 0000000..2e1bf28
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/gtinTrait.php
@@ -0,0 +1,27 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait gtinTrait {
+	/**
+	 * @var string A Global Trade Item Number (GTIN). GTINs identify trade items, including products and services, using numeric identification codes.
+	 *
+	 * A correct gtin value should be a valid GTIN, which means that it should be an all-numeric string of either 8, 12, 13 or 14 digits, or a "GS1 Digital Link" URL based on such a string. The numeric component should also have a valid GS1 check digit and meet the other rules for valid GTINs. See also GS1's GTIN Summary and Wikipedia for more details. Left-padding of the gtin values is not required or encouraged. The gtin property generalizes the earlier gtin8, gtin12, gtin13, and gtin14 properties.
+	 *
+	 * The GS1 digital link specifications expresses GTINs as URLs (URIs, IRIs, etc.). Digital Links should be populated into the hasGS1DigitalLink attribute.
+	 *
+	 * Note also that this is a definition for how to include GTINs in Schema.org data, and not a definition of GTINs in general - see the GS1 documentation for authoritative details.
+	 */
+	protected string $gtin;
+
+	public function getGtin():?string
+	{
+		return $this->gtin??null;
+	}
+	public function setGtin(string $gtin):void
+	{
+		$this->gtin = $gtin;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/hasAdultConsiderationTrait.php b/inc/managers/SEO/render/Traits/_Properties/hasAdultConsiderationTrait.php
new file mode 100644
index 0000000..a55d80a
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/hasAdultConsiderationTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\AdultOrientedEnumeration;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait hasAdultConsiderationTrait {
+	/**
+	 * @var AdultOrientedEnumeration Used to tag an item to be intended or suitable for consumption or use by adults only.
+	 */
+	protected AdultOrientedEnumeration $hasAdultConsideration;
+
+	public function getHasAdultConsideration():?AdultOrientedEnumeration
+	{
+		return $this->hasAdultConsideration??null;
+	}
+	public function setHasAdultConsideration(AdultOrientedEnumeration $hasAdultConsideration):void
+	{
+		$this->hasAdultConsideration = $hasAdultConsideration;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/hasCategoryCodeTrait.php b/inc/managers/SEO/render/Traits/_Properties/hasCategoryCodeTrait.php
new file mode 100644
index 0000000..cd61b5f
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/hasCategoryCodeTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\CategoryCode;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait hasCategoryCodeTrait {
+	use arrayHelper;
+	/**
+	 * @var CategoryCode|array A Category code contained in this code set.
+	 */
+	protected CategoryCode|array $hasCategoryCode;
+
+	public function getHasCategoryCode():CategoryCode|array|null
+	{
+		return $this->hasCategoryCode??null;
+	}
+	public function setHasCategoryCode(CategoryCode|array $hasCategoryCode):void
+	{
+		if (is_array($hasCategoryCode)) {
+			$hasCategoryCode = $this->classArray('hasCategoryCode', $hasCategoryCode, 'JVBase\managers\SEO\render\Thing\Intangible\CategoryCode');
+		}
+		$this->hasCategoryCode = $hasCategoryCode;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/hasCertificationTrait.php b/inc/managers/SEO/render/Traits/_Properties/hasCertificationTrait.php
new file mode 100644
index 0000000..6b68be9
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/hasCertificationTrait.php
@@ -0,0 +1,40 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\Certification;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait hasCertificationTrait {
+	use arrayHelper;
+
+	/**
+	 * @var Certification|array Certification information about a product, organization, service, place, or person.
+	 */
+	protected Certification|array $hasCertification;
+
+	public function getHasCertification():Certification|array|null
+	{
+		return $this->hasCertification??null;
+	}
+	public function setHasCertification(Certification|array $hasCertification):void
+	{
+		if (is_array($hasCertification)){
+			$hasCertification = $this->classArray('hasCertification', $hasCertification, 'JVBase\managers\SEO\render\Thing\CreativeWork\Certification');
+		}
+		$this->hasCertification = $hasCertification;
+	}
+
+	public function getHasCertificationFieldConfig():array
+	{
+		return [
+			'type'	=> 'repeater',
+			'label'	=> 'Has Certification',
+			'fields'	=> [
+
+			]
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/hasCredentialTrait.php b/inc/managers/SEO/render/Traits/_Properties/hasCredentialTrait.php
new file mode 100644
index 0000000..80ee56e
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/hasCredentialTrait.php
@@ -0,0 +1,34 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\EducationalOccupationalCredential;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait hasCredentialTrait {
+	/**
+	 * @var EducationalOccupationalCredential A credential awarded to the Person or Organization.
+	 */
+	protected EducationalOccupationalCredential $hasCredential;
+
+	public function getHasCredential():?EducationalOccupationalCredential
+	{
+		return $this->hasCredential??null;
+	}
+	public function setHasCredential(EducationalOccupationalCredential $hasCredential):void
+	{
+		$this->hasCredential = $hasCredential;
+	}
+
+	public function getHasCredentialFieldConfig():array
+	{
+		return [
+			'type'	=> 'repeater',
+			'label'	=> 'Has Credential',
+			'fields'	=> [
+
+			]
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/hasDefinedTermTrait.php b/inc/managers/SEO/render/Traits/_Properties/hasDefinedTermTrait.php
new file mode 100644
index 0000000..f1ac48b
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/hasDefinedTermTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\DefinedTerm;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait hasDefinedTermTrait {
+	use arrayHelper;
+	/**
+	 * @var DefinedTerm|array A Defined Term contained in this term set.
+	 */
+	protected DefinedTerm|array $hasDefinedTerm;
+
+	public function getHasDefinedTerm():DefinedTerm|array|null
+	{
+		return $this->hasDefinedTerm??null;
+	}
+	public function setHasDefinedTerm(DefinedTerm|array $hasDefinedTerm):void
+	{
+		if (is_array($hasDefinedTerm)) {
+			$hasDefinedTerm = $this->classArray('hasDefinedTerm', $hasDefinedTerm, 'JVBase\managers\SEO\render\Thing\Intangible\DefinedTerm');
+		}
+		$this->hasDefinedTerm = $hasDefinedTerm;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/hasDriveThroughServiceTrait.php b/inc/managers/SEO/render/Traits/_Properties/hasDriveThroughServiceTrait.php
new file mode 100644
index 0000000..cc5e667
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/hasDriveThroughServiceTrait.php
@@ -0,0 +1,29 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait hasDriveThroughServiceTrait {
+	/**
+	 * @var bool Indicates whether some facility (e.g. FoodEstablishment, CovidTestingFacility) offers a service that can be used by driving through in a car. In the case of CovidTestingFacility such facilities could potentially help with social distancing from other potentially-infected users.
+	 */
+	protected bool $hasDriveThroughService;
+
+	public function getHasDriveThroughService():?bool
+	{
+		return $this->hasDriveThroughService??null;
+	}
+	public function setHasDriveThroughService(bool $hasDriveThroughService):void
+	{
+		$this->hasDriveThroughService = $hasDriveThroughService;
+	}
+
+	public function getHasDriveThroughServiceFieldConfig():array
+	{
+		return [
+			'type'	=> 'true_false',
+			'label'	=> 'Has Drive Through Service',
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/hasMapTrait.php b/inc/managers/SEO/render/Traits/_Properties/hasMapTrait.php
new file mode 100644
index 0000000..f1b2713
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/hasMapTrait.php
@@ -0,0 +1,33 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait hasMapTrait {
+	/**
+	 * @var string A hasMap of the item
+	 */
+	protected string $hasMap;
+
+	public function getHasMap():?string
+	{
+		return $this->hasMap??null;
+	}
+	public function setHasMap(string $hasMap):void
+	{
+		if (!filter_var($hasMap, FILTER_VALIDATE_URL)) {
+			error_log('[SEO]Invalid map url: '.$hasMap);
+		}
+		$this->hasMap = $hasMap;
+	}
+
+	public function getHasMapFieldConfig():array
+	{
+		return [
+			'type'	=> 'url',
+			'label'	=> 'Has Map',
+			'hint'	=> 'A link to the Google Map location, if available'
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/hasMeasurementTrait.php b/inc/managers/SEO/render/Traits/_Properties/hasMeasurementTrait.php
new file mode 100644
index 0000000..be9a62f
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/hasMeasurementTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait hasMeasurementTrait {
+	use arrayHelper;
+	/**
+	 * @var QuantitativeValue|array A measurement of an item, For example, the inseam of pants, the wheel size of a bicycle, the gauge of a screw, or the carbon footprint measured for certification by an authority. Usually an exact measurement, but can also be a range of measurements for adjustable products, for example belts and ski bindings.
+	 */
+	protected QuantitativeValue|array $hasMeasurement;
+
+	public function getHasMeasurement():QuantitativeValue|array|null
+	{
+		return $this->hasMeasurement??null;
+	}
+	public function setHasMeasurement(QuantitativeValue|array $hasMeasurement):void
+	{
+		if (is_array($hasMeasurement)){
+			$hasMeasurement = $this->classArray('hasMeasurement', $hasMeasurement, 'JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue');
+		}
+		$this->hasMeasurement = $hasMeasurement;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/hasMenuItemTrait.php b/inc/managers/SEO/render/Traits/_Properties/hasMenuItemTrait.php
new file mode 100644
index 0000000..583d18f
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/hasMenuItemTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\MenuItem;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait hasMenuItemTrait {
+	use arrayHelper;
+	/**
+	 * @var MenuItem|array A food or drink item contained in a menu or menu section.
+	 */
+	protected MenuItem|array $hasMenuItem;
+
+	public function getHasMenuItem():MenuItem|array|null
+	{
+		return $this->hasMenuItem??null;
+	}
+	public function setHasMenuItem(MenuItem|array $hasMenuItem):void
+	{
+		if (is_array($hasMenuItem)) {
+			$hasMenuItem = $this->classArray('hasMenuItem', $hasMenuItem, 'JVBase\managers\SEO\render\Thing\Intangible\MenuItem');
+		}
+		$this->hasMenuItem = $hasMenuItem;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/hasMenuSectionTrait.php b/inc/managers/SEO/render/Traits/_Properties/hasMenuSectionTrait.php
new file mode 100644
index 0000000..f2a4509
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/hasMenuSectionTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\MenuSection;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait hasMenuSectionTrait {
+	use arrayHelper;
+	/**
+	 * @var MenuSection|array A subgrouping of the menu (by dishes, course, serving time period, etc.).
+	 */
+	protected MenuSection|array $hasMenuSection;
+
+	public function getHasMenuSection():MenuSection|array|null
+	{
+		return $this->hasMenuSection??null;
+	}
+	public function setHasMenuSection(MenuSection|array $hasMenuSection):void
+	{
+		if (is_array($hasMenuSection)) {
+			$hasMenuSection = $this->classArray('hasMenuSection', $hasMenuSection, 'JVBase\managers\SEO\render\Thing\CreativeWork\MenuSection');
+		}
+		$this->hasMenuSection = $hasMenuSection;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/hasMenuTrait.php b/inc/managers/SEO/render/Traits/_Properties/hasMenuTrait.php
new file mode 100644
index 0000000..442ab02
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/hasMenuTrait.php
@@ -0,0 +1,32 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\Menu;
+use JVBase\managers\SEO\render\Thing\CreativeWork\MenuSection;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait hasMenuTrait {
+	/**
+	 * @var Menu|string Either the actual menu as a structured representation, as text, or a URL of the menu.
+	 * Supersedes menu.
+	 */
+	protected Menu|string $hasMenu;
+
+	public function getHasMenu():Menu|string|null
+	{
+		return $this->hasMenu??null;
+	}
+	public function setHasMenu(Menu|string $hasMenu):void
+	{
+		if (is_string($hasMenu)) {
+			if (!filter_var($hasMenu, FILTER_VALIDATE_URL)) {
+				error_log('[SEO]HasMenu intended to be a url pointing to the menu, or a Menu object.');
+				return;
+			}
+		}
+		$this->hasMenu = $hasMenu;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/hasMerchantReturnPolicyTrait.php b/inc/managers/SEO/render/Traits/_Properties/hasMerchantReturnPolicyTrait.php
new file mode 100644
index 0000000..9d756b6
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/hasMerchantReturnPolicyTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\MerchantReturnPolicy;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait hasMerchantReturnPolicyTrait {
+	/**
+	 * @var MerchantReturnPolicy Specifies a MerchantReturnPolicy that may be applicable. Supersedes hasProductReturnPolicy.
+	 */
+	protected MerchantReturnPolicy $hasMerchantReturnPolicy;
+
+	public function getHasMerchantReturnPolicy():?MerchantReturnPolicy
+	{
+		return $this->hasMerchantReturnPolicy??null;
+	}
+	public function setHasMerchantReturnPolicy(MerchantReturnPolicy $hasMerchantReturnPolicy):void
+	{
+		$this->hasMerchantReturnPolicy = $hasMerchantReturnPolicy;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/hasOccupationTrait.php b/inc/managers/SEO/render/Traits/_Properties/hasOccupationTrait.php
new file mode 100644
index 0000000..3c2f9c5
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/hasOccupationTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Occupation;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait hasOccupationTrait {
+	use arrayHelper;
+	/**
+	 * @var Occupation|array The Person's occupation. For past professions, use Role for expressing dates.
+	 */
+	protected Occupation|array $hasOccupation;
+
+	public function getHasOccupation():Occupation|array|null
+	{
+		return $this->hasOccupation??null;
+	}
+	public function setHasOccupation(Occupation|array $hasOccupation):void
+	{
+		if (is_array($hasOccupation)) {
+			$hasOccupation = $this->classArray('hasOccupation', $hasOccupation, 'JVBase\managers\SEO\render\Thing\Intangible\Occupation');
+		}
+		$this->hasOccupation = $hasOccupation;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/hasOfferCatalogTrait.php b/inc/managers/SEO/render/Traits/_Properties/hasOfferCatalogTrait.php
new file mode 100644
index 0000000..90f336a
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/hasOfferCatalogTrait.php
@@ -0,0 +1,76 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\OfferCatalog;
+use JVBase\registrar\Registrar;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait hasOfferCatalogTrait {
+	/**
+	 * @var OfferCatalog Indicates an OfferCatalog listing for this Organization, Person, or Service.
+	 */
+	protected OfferCatalog $hasOfferCatalog;
+
+	public function getHasOfferCatalog():?OfferCatalog
+	{
+		return $this->hasOfferCatalog??null;
+	}
+	public function setHasOfferCatalog(OfferCatalog $hasOfferCatalog):void
+	{
+		$this->hasOfferCatalog = $hasOfferCatalog;
+	}
+
+	public function getHasOfferCatalogFieldConfig():array
+	{
+		return [
+			'type'	=> 'group',
+			'label'	=> 'Offer Catalog',
+			'wrap'	=> 'details',
+			'fields'	=> [
+				'generate'	=> [
+					'type'	=> 'true_false',
+					'label'	=> 'Generate from Content type?'
+				],
+				'content_type'	=> [
+					'type'	=> 'checkbox',
+					'label'	=> 'Select Type to generate from',
+					'options'	=> Registrar::getRegistered(),
+					'condition'	=> [
+						'field'	=> 'generate',
+						'value'	=> 1,
+						'operator'	=> '=='
+					]
+				],
+				'offer_catalog'	=> [
+					'type'	=> 'repeater',
+					'label'	=> 'Manually Enter Offer Catalog',
+					'condition'	=> [
+						'field'	=> 'generate',
+						'value'	=> 1,
+						'operator'	=> '!='
+					],
+					'fields'	=> [
+						'type'	=> [
+							'type'	=> 'select',
+							'label'	=> 'Type',
+							'options'	=> [
+								'product'	=> 'Product',
+								'service'	=> 'Service'
+							]
+						],
+						'name'	=> [
+							'type'	=> 'text',
+							'label'	=> 'Name',
+						],
+						'description'	=> [
+							'type'	=> 'textarea',
+							'label'	=> 'Description'
+						]
+					]
+				]
+			]
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/hasPOSTrait.php b/inc/managers/SEO/render/Traits/_Properties/hasPOSTrait.php
new file mode 100644
index 0000000..77ec165
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/hasPOSTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait hasPOSTrait {
+	/**
+	 * @var Place Points-of-Sales operated by the organization or person.
+	 */
+	protected Place $hasPOS;
+
+	public function getHasPOS():?Place
+	{
+		return $this->hasPOS??null;
+	}
+	public function setHasPOS(Place $hasPOS):void
+	{
+		$this->hasPOS = $hasPOS;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/hasPartTrait.php b/inc/managers/SEO/render/Traits/_Properties/hasPartTrait.php
new file mode 100644
index 0000000..8d4b05e
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/hasPartTrait.php
@@ -0,0 +1,29 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait hasPartTrait {
+	use arrayHelper;
+	/**
+	 * @var CreativeWork|array Indicates an item or CreativeWork that is part of this item, or CreativeWork (in some sense).
+	 * Inverse property: isPartOf
+	 */
+	protected CreativeWork|array $hasPart;
+
+	public function getHasPart():CreativeWork|array|null
+	{
+		return $this->hasPart??null;
+	}
+	public function setHasPart(CreativeWork|array $hasPart):void
+	{
+		if (is_array($hasPart)) {
+			$hasPart = $this->classArray('hasPart', $hasPart, 'JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork');
+		}
+		$this->hasPart = $hasPart;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/hasVariantTrait.php b/inc/managers/SEO/render/Traits/_Properties/hasVariantTrait.php
new file mode 100644
index 0000000..a65a6e3
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/hasVariantTrait.php
@@ -0,0 +1,29 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Product\Product;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait hasVariantTrait {
+	use arrayHelper;
+	/**
+	 * @var Product|array Indicates a Product that is a member of this ProductGroup (or ProductModel).
+	 * Inverse property: isVariantOf
+	 */
+	protected Product|array $hasVariant;
+
+	public function getHasVariant():Product|array|null
+	{
+		return $this->hasVariant??null;
+	}
+	public function setHasVariant(Product|array $hasVariant):void
+	{
+		if (is_array($hasVariant)) {
+			$hasVariant = $this->classArray('hasVariant', $hasVariant, 'JVBase\managers\SEO\render\Thing\Product\Product');
+		}
+		$this->hasVariant = $hasVariant;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/headlineTrait.php b/inc/managers/SEO/render/Traits/_Properties/headlineTrait.php
new file mode 100644
index 0000000..0281de2
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/headlineTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait headlineTrait {
+	/**
+	 * @var string Headline of the article.
+	 */
+	protected string $headline;
+
+	public function getHeadline():?string
+	{
+		return $this->headline??null;
+	}
+	public function setHeadline(string $headline):void
+	{
+		$this->headline = $headline;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/heightTrait.php b/inc/managers/SEO/render/Traits/_Properties/heightTrait.php
new file mode 100644
index 0000000..30c0c01
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/heightTrait.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Quantity\Distance;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait heightTrait {
+	/**
+	 * @var Distance|QuantitativeValue The height of the item.
+	 */
+	protected Distance|QuantitativeValue $height;
+
+	public function getHeight():Distance|QuantitativeValue|null
+	{
+		return $this->height??null;
+	}
+	public function setHeight(Distance|QuantitativeValue $height):void
+	{
+		$this->height = $height;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/highPriceTrait.php b/inc/managers/SEO/render/Traits/_Properties/highPriceTrait.php
new file mode 100644
index 0000000..d24033c
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/highPriceTrait.php
@@ -0,0 +1,26 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait highPriceTrait {
+	/**
+	 * @var int|float The highest price of all offers available.
+	 *
+	 * Usage guidelines:
+	 *
+	 * Use values from 0123456789 (Unicode 'DIGIT ZERO' (U+0030) to 'DIGIT NINE' (U+0039)) rather than superficially similar Unicode symbols.
+	 * Use '.' (Unicode 'FULL STOP' (U+002E)) rather than ',' to indicate a decimal point. Avoid using these symbols as a readability separator.
+	 */
+	protected int|float $highPrice;
+
+	public function getHighPrice():int|float|null
+	{
+		return $this->highPrice??null;
+	}
+	public function setHighPrice(int|float $highPrice):void
+	{
+		$this->highPrice = $highPrice;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/homeLocationTrait.php b/inc/managers/SEO/render/Traits/_Properties/homeLocationTrait.php
new file mode 100644
index 0000000..f41ad78
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/homeLocationTrait.php
@@ -0,0 +1,27 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\ContactPoint\PostalAddress;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\ContactPoint;
+use JVBase\managers\SEO\render\Thing\Intangible\VirtualHomeLocation;
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait homeLocationTrait {
+	/**
+	 * @var ContactPoint|Place A contact location for a person's residence.
+	 */
+	protected ContactPoint|Place $homeLocation;
+
+	public function getHomeLocation():ContactPoint|Place|null
+	{
+		return $this->homeLocation??null;
+	}
+	public function setHomeLocation(ContactPoint|Place $homeLocation):void
+	{
+		$this->homeLocation = $homeLocation;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/honorificPrefixTrait.php b/inc/managers/SEO/render/Traits/_Properties/honorificPrefixTrait.php
new file mode 100644
index 0000000..19f65ac
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/honorificPrefixTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait honorificPrefixTrait {
+	/**
+	 * @var string An honorific prefix preceding a Person's name such as Dr/Mrs/Mr.
+	 */
+	protected string $honorificPrefix;
+
+	public function getHonorificPrefix():?string
+	{
+		return $this->honorificPrefix??null;
+	}
+	public function setHonorificPrefix(string $honorificPrefix):void
+	{
+		$this->honorificPrefix = $honorificPrefix;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/honorificSuffixTrait.php b/inc/managers/SEO/render/Traits/_Properties/honorificSuffixTrait.php
new file mode 100644
index 0000000..0b61e97
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/honorificSuffixTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait honorificSuffixTrait {
+	/**
+	 * @var string An honorific suffix following a Person's name such as M.D./PhD/MSCSW.
+	 */
+	protected string $honorificSuffix;
+
+	public function getHonorificSuffix():?string
+	{
+		return $this->honorificSuffix??null;
+	}
+	public function setHonorificSuffix(string $honorificSuffix):void
+	{
+		$this->honorificSuffix = $honorificSuffix;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/hoursAvailableTrait.php b/inc/managers/SEO/render/Traits/_Properties/hoursAvailableTrait.php
new file mode 100644
index 0000000..aa303ff
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/hoursAvailableTrait.php
@@ -0,0 +1,25 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\OpeningHoursSpecification;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait hoursAvailableTrait {
+	use arrayHelper;
+	/**
+	 * @var OpeningHoursSpecification The hours during which this service or contact is available.
+	 */
+	protected OpeningHoursSpecification $hoursAvailable;
+
+	public function getHoursAvailable():?OpeningHoursSpecification
+	{
+		return $this->hoursAvailable??null;
+	}
+	public function setHoursAvailable(OpeningHoursSpecification $hoursAvailable):void
+	{
+		$this->hoursAvailable = $hoursAvailable;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/httpMethodTrait.php b/inc/managers/SEO/render/Traits/_Properties/httpMethodTrait.php
new file mode 100644
index 0000000..e6af20b
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/httpMethodTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait httpMethodTrait {
+	/**
+	 * @var string An HTTP method that specifies the appropriate HTTP method for a request to an HTTP EntryPoint. Values are capitalized strings as used in HTTP.
+	 */
+	protected string $httpMethod;
+
+	public function getHttpMethod():?string
+	{
+		return $this->httpMethod??null;
+	}
+	public function setHttpMethod(string $httpMethod):void
+	{
+		$this->httpMethod = $httpMethod;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/imageTrait.php b/inc/managers/SEO/render/Traits/_Properties/imageTrait.php
new file mode 100644
index 0000000..44e8d6f
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/imageTrait.php
@@ -0,0 +1,33 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\MediaObject\ImageObject;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait imageTrait {
+	/**
+	 * @var string|ImageObject Can be a URL, or a fully described ImageObject
+	 */
+	protected string|ImageObject $image;
+
+	public function getImage():?string
+	{
+		return $this->image??null;
+	}
+	public function setImage(string|ImageObject $image):void
+	{
+		$this->image = $image;
+	}
+
+	public function getImageFieldConfig():array
+	{
+		return [
+			'type'	=> 'upload',
+			'multiple'	=> false,
+			'label'	=> 'Image',
+			'hint'	=> 'An image of the Item.'
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/inCodeSetTrait.php b/inc/managers/SEO/render/Traits/_Properties/inCodeSetTrait.php
new file mode 100644
index 0000000..b42269d
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/inCodeSetTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CategoryCodeSet;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait inCodeSetTrait {
+	/**
+	 * @var CategoryCodeSet|string A CategoryCodeSet that contains this category code.
+	 */
+	protected CategoryCodeSet|string $inCodeSet;
+
+	public function getInCodeSet():CategoryCodeSet|string|null
+	{
+		return $this->inCodeSet??null;
+	}
+	public function setInCodeSet(CategoryCodeSet|string $inCodeSet):void
+	{
+		$this->inCodeSet = $inCodeSet;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/inDefinedTermSetTrait.php b/inc/managers/SEO/render/Traits/_Properties/inDefinedTermSetTrait.php
new file mode 100644
index 0000000..6cfd565
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/inDefinedTermSetTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\DefinedTermSet;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait inDefinedTermSetTrait {
+	/**
+	 * @var 	DefinedTermSet|string A DefinedTermSet that contains this term.
+	 */
+	protected DefinedTermSet|string $inDefinedTermSet;
+
+	public function getInDefinedTermSet():DefinedTermSet|string|null
+	{
+		return $this->inDefinedTermSet??null;
+	}
+	public function setInDefinedTermSet(DefinedTermSet|string $inDefinedTermSet):void
+	{
+		$this->inDefinedTermSet = $inDefinedTermSet;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/inGroupProductWithIDTrait.php b/inc/managers/SEO/render/Traits/_Properties/inGroupProductWithIDTrait.php
new file mode 100644
index 0000000..357d445
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/inGroupProductWithIDTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait inGroupProductWithIDTrait {
+	/**
+	 * @var string Indicates the productGroupID for a ProductGroup that this product isVariantOf.
+	 */
+	protected string $inGroupProductWithID;
+
+	public function getInGroupProductWithID():?string
+	{
+		return $this->inGroupProductWithID??null;
+	}
+	public function setInGroupProductWithID(string $inGroupProductWithID):void
+	{
+		$this->inGroupProductWithID = $inGroupProductWithID;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/inLanguageTrait.php b/inc/managers/SEO/render/Traits/_Properties/inLanguageTrait.php
new file mode 100644
index 0000000..3d599ab
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/inLanguageTrait.php
@@ -0,0 +1,31 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Language;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait inLanguageTrait {
+	use arrayHelper;
+	/**
+	 * @var Language|string|array The language of the content or performance or used in an action. Please use one of the language codes from the IETF BCP 47 standard. See also availableLanguage. Supersedes language.
+	 */
+	protected Language|string|array $inLanguage;
+
+	public function getInLanguage():Language|string|array|null
+	{
+		return $this->inLanguage??null;
+	}
+	public function setInLanguage(Language|string|array $inLanguage):void
+	{
+		if (is_array($inLanguage)) {
+			$inLanguage = $this->mixedArray('inLanguage', $inLanguage, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\Intangible\Language'
+			]);
+		}
+		$this->inLanguage = $inLanguage;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/inStoreReturnsOfferedTrait.php b/inc/managers/SEO/render/Traits/_Properties/inStoreReturnsOfferedTrait.php
new file mode 100644
index 0000000..323f49f
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/inStoreReturnsOfferedTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait inStoreReturnsOfferedTrait {
+	/**
+	 * @var bool Are in-store returns offered? (For more advanced return methods use the returnMethod property.)
+	 */
+	protected bool $inStoreReturnsOffered;
+
+	public function getInStoreReturnsOffered():?bool
+	{
+		return $this->inStoreReturnsOffered??null;
+	}
+	public function setInStoreReturnsOffered(bool $inStoreReturnsOffered):void
+	{
+		$this->inStoreReturnsOffered = $inStoreReturnsOffered;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/includesObjectTrait.php b/inc/managers/SEO/render/Traits/_Properties/includesObjectTrait.php
new file mode 100644
index 0000000..352ad35
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/includesObjectTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\TypeAndQuantityNode;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait includesObjectTrait {
+	use arrayHelper;
+	/**
+	 * @var TypeAndQuantityNode|array This links to a node or nodes indicating the exact quantity of the products included in an Offer or ProductCollection.
+	 */
+	protected TypeAndQuantityNode|array $includesObject;
+
+	public function getIncludesObject():TypeAndQuantityNode|array|null
+	{
+		return $this->includesObject??null;
+	}
+	public function setIncludesObject(TypeAndQuantityNode|array $includesObject):void
+	{
+		if (is_array($includesObject)) {
+			$includesObject = $this->classArray('includesObject', $includesObject, 'JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\TypeAndQuantityNode');
+		}
+		$this->includesObject = $includesObject;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/ineligibleLocationTrait.php b/inc/managers/SEO/render/Traits/_Properties/ineligibleLocationTrait.php
new file mode 100644
index 0000000..dc94ae9
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/ineligibleLocationTrait.php
@@ -0,0 +1,33 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\GeoShape;
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait ineligibleLocationTrait {
+	use arrayHelper;
+	/**
+	 * @var GeoShape|Place|string|array The ISO 3166-1 (ISO 3166-1 alpha-2) or ISO 3166-2 code, the place, or the GeoShape for the geo-political region(s) for which the offer or delivery charge specification is not valid, e.g. a region where the transaction is not allowed.
+	 */
+	protected GeoShape|Place|string|array $ineligibleLocation;
+
+	public function getIneligibleLocation():GeoShape|Place|string|array|null
+	{
+		return $this->ineligibleLocation??null;
+	}
+	public function setIneligibleLocation(GeoShape|Place|string|array $ineligibleLocation):void
+	{
+		if (is_array($ineligibleLocation)) {
+			$ineligibleLocation = $this->mixedArray('ineligibleLocation', $ineligibleLocation, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\GeoShape',
+				'JVBase\managers\SEO\render\Thing\Place\Place'
+			]);
+		}
+		$this->ineligibleLocation = $ineligibleLocation;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/ineligibleRegionTrait.php b/inc/managers/SEO/render/Traits/_Properties/ineligibleRegionTrait.php
new file mode 100644
index 0000000..129a6c5
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/ineligibleRegionTrait.php
@@ -0,0 +1,33 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\GeoShape;
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait ineligibleRegionTrait {
+	use arrayHelper;
+	/**
+	 * @var GeoShape|Place|string|array The ISO 3166-1 (ISO 3166-1 alpha-2) or ISO 3166-2 code, the place, or the GeoShape for the geo-political region(s) for which the offer or delivery charge specification is not valid, e.g. a region where the transaction is not allowed.
+	 */
+	protected GeoShape|Place|string|array $ineligibleRegion;
+
+	public function getIneligibleRegion():GeoShape|Place|string|array|null
+	{
+		return $this->ineligibleRegion??null;
+	}
+	public function setIneligibleRegion(GeoShape|Place|string|array $ineligibleRegion):void
+	{
+		if (is_array($ineligibleRegion)) {
+			$ineligibleRegion = $this->mixedArray('ineligibleRegion', $ineligibleRegion, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\GeoShape',
+				'JVBase\managers\SEO\render\Thing\Place\Place'
+			]);
+		}
+		$this->ineligibleRegion = $ineligibleRegion;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/instrumentTrait.php b/inc/managers/SEO/render/Traits/_Properties/instrumentTrait.php
new file mode 100644
index 0000000..4c2e02a
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/instrumentTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait instrumentTrait {
+	/**
+	 * @var Thing The object that helped the agent perform the action. E.g. John wrote a book with a pen.
+	 */
+	protected Thing $instrument;
+
+	public function getInstrument():?Thing
+	{
+		return $this->instrument??null;
+	}
+	public function setInstrument(Thing $instrument):void
+	{
+		$this->instrument = $instrument;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/interactionServiceTrait.php b/inc/managers/SEO/render/Traits/_Properties/interactionServiceTrait.php
new file mode 100644
index 0000000..38d61cf
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/interactionServiceTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\WebSite;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait interactionServiceTrait {
+	/**
+	 * @var WebSite The WebSite or SoftwareApplication where the interactions took place. Also accepts SoftwareApplication (not included)
+	 */
+	protected WebSite $interactionService;
+
+	public function getInteractionService():?WebSite
+	{
+		return $this->interactionService??null;
+	}
+	public function setInteractionService(WebSite $interactionService):void
+	{
+		$this->interactionService = $interactionService;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/interactionStatisticTrait.php b/inc/managers/SEO/render/Traits/_Properties/interactionStatisticTrait.php
new file mode 100644
index 0000000..2ee05bf
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/interactionStatisticTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\InteractionCounter;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait interactionStatisticTrait {
+	/**
+	 * @var InteractionCounter The number of interactions for the CreativeWork using the WebSite or SoftwareApplication. The most specific child type of InteractionCounter should be used. Supersedes interactionCount.
+	 */
+	protected InteractionCounter $interactionStatistic;
+
+	public function getInteractionStatistic():?InteractionCounter
+	{
+		return $this->interactionStatistic??null;
+	}
+	public function setInteractionStatistic(InteractionCounter $interactionStatistic):void
+	{
+		$this->interactionStatistic = $interactionStatistic;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/interactionTypeTrait.php b/inc/managers/SEO/render/Traits/_Properties/interactionTypeTrait.php
new file mode 100644
index 0000000..717e73e
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/interactionTypeTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Action;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait interactionTypeTrait {
+	/**
+	 * @var Action The Action representing the type of interaction. For up votes, +1s, etc. use LikeAction. For down votes use DislikeAction. Otherwise, use the most specific Action.
+	 */
+	protected Action $interactionType;
+
+	public function getInteractionType():?Action
+	{
+		return $this->interactionType??null;
+	}
+	public function setInteractionType(Action $interactionType):void
+	{
+		$this->interactionType = $interactionType;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/inventoryLevelTrait.php b/inc/managers/SEO/render/Traits/_Properties/inventoryLevelTrait.php
new file mode 100644
index 0000000..83d1f8d
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/inventoryLevelTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait inventoryLevelTrait {
+	/**
+	 * @var QuantitativeValue The current approximate inventory level for the item or items.
+	 */
+	protected QuantitativeValue $inventoryLevel;
+
+	public function getInventoryLevel():?QuantitativeValue
+	{
+		return $this->inventoryLevel??null;
+	}
+	public function setInventoryLevel(QuantitativeValue $inventoryLevel):void
+	{
+		$this->inventoryLevel = $inventoryLevel;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/isAccessibleForFreeTrait.php b/inc/managers/SEO/render/Traits/_Properties/isAccessibleForFreeTrait.php
new file mode 100644
index 0000000..5386797
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/isAccessibleForFreeTrait.php
@@ -0,0 +1,29 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait isAccessibleForFreeTrait {
+	/**
+	 * @var bool A flag to signal that the item, event, or place is accessible for free. Supersedes free.
+	 */
+	protected bool $isAccessibleForFree;
+
+	public function getIsAccessibleForFree():?bool
+	{
+		return $this->isAccessibleForFree??null;
+	}
+	public function setIsAccessibleForFree(bool $isAccessibleForFree):void
+	{
+		$this->isAccessibleForFree = $isAccessibleForFree;
+	}
+
+	public function getIsAccessibleForFreeFieldConfig():array
+	{
+		return [
+			'type'	=> 'true_false',
+			'label'	=> 'Is accessible for Free',
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/isAccessoryOrSparePartForTrait.php b/inc/managers/SEO/render/Traits/_Properties/isAccessoryOrSparePartForTrait.php
new file mode 100644
index 0000000..d1cb10d
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/isAccessoryOrSparePartForTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Product\Product;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait isAccessoryOrSparePartForTrait {
+	use arrayHelper;
+	/**
+	 * @var Product|array A pointer to another product (or multiple products) for which this product is an accessory or spare part.
+	 */
+	protected Product|array $isAccessoryOrSparePartFor;
+
+	public function getIsAccessoryOrSparePartFor():Product|array|null
+	{
+		return $this->isAccessoryOrSparePartFor??null;
+	}
+	public function setIsAccessoryOrSparePartFor(Product|array $isAccessoryOrSparePartFor):void
+	{
+		if (is_array($isAccessoryOrSparePartFor)) {
+			$isAccessoryOrSparePartFor = $this->classArray('isAccessoryOrSparePartFor', $isAccessoryOrSparePartFor, 'JVBase\managers\SEO\render\Thing\Product\Product');
+		}
+		$this->isAccessoryOrSparePartFor = $isAccessoryOrSparePartFor;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/isBasedOnTrait.php b/inc/managers/SEO/render/Traits/_Properties/isBasedOnTrait.php
new file mode 100644
index 0000000..12e7b45
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/isBasedOnTrait.php
@@ -0,0 +1,33 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+use JVBase\managers\SEO\render\Thing\Product\Product;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait isBasedOnTrait {
+	use arrayHelper;
+	/**
+	 * @var CreativeWork|Product|string|array A resource from which this work is derived or from which it is a modification or adaptation. Supersedes isBasedOnUrl.
+	 */
+	protected CreativeWork|Product|string|array $isBasedOn;
+
+	public function getIsBasedOn():CreativeWork|Product|string|array|null
+	{
+		return $this->isBasedOn??null;
+	}
+	public function setIsBasedOn(CreativeWork|Product|string|array $isBasedOn):void
+	{
+		if (is_array($isBasedOn)) {
+			$isBasedOn = $this->mixedArray('isBasedOn', $isBasedOn, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork',
+				'JVBase\managers\SEO\render\Thing\Product\Product'
+			]);
+		}
+		$this->isBasedOn = $isBasedOn;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/isConsumableForTrait.php b/inc/managers/SEO/render/Traits/_Properties/isConsumableForTrait.php
new file mode 100644
index 0000000..da528ef
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/isConsumableForTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Product\Product;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait isConsumableForTrait {
+	use arrayHelper;
+	/**
+	 * @var Product|array A pointer to another product (or multiple products) for which this product is an accessory or spare part.
+	 */
+	protected Product|array $isConsumableFor;
+
+	public function getIsConsumableFor():Product|array|null
+	{
+		return $this->isConsumableFor??null;
+	}
+	public function setIsConsumableFor(Product|array $isConsumableFor):void
+	{
+		if (is_array($isConsumableFor)) {
+			$isConsumableFor = $this->classArray('isConsumableFor', $isConsumableFor, 'JVBase\managers\SEO\render\Thing\Product\Product');
+		}
+		$this->isConsumableFor = $isConsumableFor;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/isFamilyFriendlyTrait.php b/inc/managers/SEO/render/Traits/_Properties/isFamilyFriendlyTrait.php
new file mode 100644
index 0000000..d22ea0c
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/isFamilyFriendlyTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait isFamilyFriendlyTrait {
+	/**
+	 * @var bool Indicates whether this content is family friendly.
+	 */
+	protected bool $isFamilyFriendly;
+
+	public function getIsFamilyFriendly():?bool
+	{
+		return $this->isFamilyFriendly??null;
+	}
+	public function setIsFamilyFriendly(bool $isFamilyFriendly):void
+	{
+		$this->isFamilyFriendly = $isFamilyFriendly;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/isPartOfTrait.php b/inc/managers/SEO/render/Traits/_Properties/isPartOfTrait.php
new file mode 100644
index 0000000..869aa55
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/isPartOfTrait.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait isPartOfTrait {
+	/**
+	 * @var CreativeWork|string Indicates an item or CreativeWork that this item, or CreativeWork (in some sense), is part of.
+	 * Inverse property: hasPart
+	 */
+	protected CreativeWork|string $isPartOf;
+
+	public function getIsPartOf():CreativeWork|string|null
+	{
+		return $this->isPartOf??null;
+	}
+	public function setIsPartOf(CreativeWork|string $isPartOf):void
+	{
+		$this->isPartOf = $isPartOf;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/isRelatedToTrait.php b/inc/managers/SEO/render/Traits/_Properties/isRelatedToTrait.php
new file mode 100644
index 0000000..7c78b78
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/isRelatedToTrait.php
@@ -0,0 +1,32 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Service;
+use JVBase\managers\SEO\render\Thing\Product\Product;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait isRelatedToTrait {
+	use arrayHelper;
+	/**
+	 * @var Product|Service|array A pointer to another, somehow related product (or multiple products).
+	 */
+	protected Product|Service|array $isRelatedTo;
+
+	public function getIsRelatedTo():Product|Service|array|null
+	{
+		return $this->isRelatedTo??null;
+	}
+	public function setIsRelatedTo(Product|Service|array $isRelatedTo):void
+	{
+		if (is_array($isRelatedTo)) {
+			$isRelatedTo = $this->mixedArray('isRelatedTo', $isRelatedTo, [
+				'JVBase\managers\SEO\render\Thing\Intangible\Service',
+				'JVBase\managers\SEO\render\Thing\Product\Product'
+			]);
+		}
+		$this->isRelatedTo = $isRelatedTo;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/isSimilarToTrait.php b/inc/managers/SEO/render/Traits/_Properties/isSimilarToTrait.php
new file mode 100644
index 0000000..ace6822
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/isSimilarToTrait.php
@@ -0,0 +1,32 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Service;
+use JVBase\managers\SEO\render\Thing\Product\Product;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait isSimilarToTrait {
+	use arrayHelper;
+	/**
+	 * @var Product|Service|array A pointer to another, somehow related product (or multiple products).
+	 */
+	protected Product|Service|array $isSimilarTo;
+
+	public function getIsSimilarTo():Product|Service|array|null
+	{
+		return $this->isSimilarTo??null;
+	}
+	public function setIsSimilarTo(Product|Service|array $isSimilarTo):void
+	{
+		if (is_array($isSimilarTo)) {
+			$isSimilarTo = $this->mixedArray('isSimilarTo', $isSimilarTo, [
+				'JVBase\managers\SEO\render\Thing\Intangible\Service',
+				'JVBase\managers\SEO\render\Thing\Product\Product'
+			]);
+		}
+		$this->isSimilarTo = $isSimilarTo;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/isVariantOfTrait.php b/inc/managers/SEO/render/Traits/_Properties/isVariantOfTrait.php
new file mode 100644
index 0000000..559bde9
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/isVariantOfTrait.php
@@ -0,0 +1,27 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Service;
+use JVBase\managers\SEO\render\Thing\Product\Product;
+use JVBase\managers\SEO\render\Thing\Product\ProductGroup;
+use JVBase\managers\SEO\render\Thing\Product\ProductModel;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait isVariantOfTrait {
+	/**
+	 * @var ProductGroup|ProductModel Indicates the kind of product that this is a variant of. In the case of ProductModel, this is a pointer (from a ProductModel) to a base product from which this product is a variant. It is safe to infer that the variant inherits all product features from the base model, unless defined locally. This is not transitive. In the case of a ProductGroup, the group description also serves as a template, representing a set of Products that vary on explicitly defined, specific dimensions only (so it defines both a set of variants, as well as which values distinguish amongst those variants). When used with ProductGroup, this property can apply to any Product included in the group.
+	 */
+	protected ProductGroup|ProductModel $isVariantOf;
+
+	public function getIsVariantOf():ProductGroup|ProductModel|null
+	{
+		return $this->isVariantOf??null;
+	}
+	public function setIsVariantOf(ProductGroup|ProductModel $isVariantOf):void
+	{
+		$this->isVariantOf = $isVariantOf;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/issnTrait.php b/inc/managers/SEO/render/Traits/_Properties/issnTrait.php
new file mode 100644
index 0000000..9eaf500
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/issnTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait issnTrait {
+	/**
+	 * @var string The International Standard Serial Number (ISSN) that identifies this serial publication. You can repeat this property to identify different formats of, or the linking ISSN (ISSN-L) for, this serial publication.
+	 */
+	protected string $issn;
+
+	public function getIssn():?string
+	{
+		return $this->issn??null;
+	}
+	public function setIssn(string $issn):void
+	{
+		$this->issn = $issn;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/issuedByTrait.php b/inc/managers/SEO/render/Traits/_Properties/issuedByTrait.php
new file mode 100644
index 0000000..9be4e73
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/issuedByTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait issuedByTrait {
+	/**
+	 * @var Organization The organization issuing the item, for example a Permit, Ticket, or Certification.
+	 */
+	protected Organization $issuedBy;
+
+	public function getIssuedBy():?Organization
+	{
+		return $this->issuedBy??null;
+	}
+	public function setIssuedBy(Organization $issuedBy):void
+	{
+		$this->issuedBy = $issuedBy;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/itemConditionTrait.php b/inc/managers/SEO/render/Traits/_Properties/itemConditionTrait.php
new file mode 100644
index 0000000..063960b
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/itemConditionTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\OfferItemCondition;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait itemConditionTrait {
+	/**
+	 * @var OfferItemCondition A predefined value from OfferItemCondition specifying the condition of the product or service, or the products or services included in the offer. Also used for product return policies to specify the condition of products accepted for returns.
+	 */
+	protected OfferItemCondition $itemCondition;
+
+	public function getItemCondition():?OfferItemCondition
+	{
+		return $this->itemCondition??null;
+	}
+	public function setItemCondition(OfferItemCondition $itemCondition):void
+	{
+		$this->itemCondition = $itemCondition;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/itemListElementTrait.php b/inc/managers/SEO/render/Traits/_Properties/itemListElementTrait.php
new file mode 100644
index 0000000..234f5d2
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/itemListElementTrait.php
@@ -0,0 +1,33 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\ListItem;
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait itemListElementTrait {
+	use arrayHelper;
+	/**
+	 * @var ListItem|Thing|string|array Text values are best if the elements in the list are plain strings. Existing entities are best for a simple, unordered list of existing things in your data. ListItem is used with ordered lists when you want to provide additional context about the element in that list or when the same item might be in different places in different lists.
+	 */
+	protected ListItem|Thing|string|array $itemListElement;
+
+	public function getItemListElement():ListItem|Thing|string|array|null
+	{
+		return $this->itemListElement??null;
+	}
+	public function setItemListElement(ListItem|Thing|string|array $itemListElement):void
+	{
+		if (is_array($itemListElement)) {
+			$itemListElement = $this->mixedArray('itemListElement', $itemListElement, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\Intangible\ListItem',
+				'JVBase\managers\SEO\render\Thing\Thing'
+			]);
+		}
+		$this->itemListElement = $itemListElement;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/itemListOrderTrait.php b/inc/managers/SEO/render/Traits/_Properties/itemListOrderTrait.php
new file mode 100644
index 0000000..3249a9c
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/itemListOrderTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\ItemListOrderType;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait itemListOrderTrait {
+	/**
+	 * @var ItemListOrderType|string Type of ordering (e.g. Ascending, Descending, Unordered).
+	 */
+	protected ItemListOrderType|string $itemListOrder;
+
+	public function getItemListOrder():ItemListOrderType|string|null
+	{
+		return $this->itemListOrder??null;
+	}
+	public function setItemListOrder(ItemListOrderType|string $itemListOrder):void
+	{
+		$this->itemListOrder = $itemListOrder;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/itemOfferedTrait.php b/inc/managers/SEO/render/Traits/_Properties/itemOfferedTrait.php
new file mode 100644
index 0000000..45c5784
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/itemOfferedTrait.php
@@ -0,0 +1,40 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+use JVBase\managers\SEO\render\Thing\Event\Event;
+use JVBase\managers\SEO\render\Thing\Intangible\AggregateOffer;
+use JVBase\managers\SEO\render\Thing\Intangible\MenuItem;
+use JVBase\managers\SEO\render\Thing\Intangible\Service;
+use JVBase\managers\SEO\render\Thing\Product\Product;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait itemOfferedTrait {
+	use arrayHelper;
+	/**
+	 * @var AggregateOffer|CreativeWork|Event|MenuItem|Product|Service|array An item being offered (or demanded). The transactional nature of the offer or demand is documented using businessFunction, e.g. sell, lease etc. While several common expected types are listed explicitly in this definition, others can be used. Using a second type, such as Product or a subtype of Product, can clarify the nature of the offer.
+	 */
+	protected AggregateOffer|CreativeWork|Event|MenuItem|Product|Service|array $itemOffered;
+
+	public function getItemOffered():AggregateOffer|CreativeWork|Event|MenuItem|Product|Service|array|null
+	{
+		return $this->itemOffered??null;
+	}
+	public function setItemOffered(AggregateOffer|CreativeWork|Event|MenuItem|Product|Service|array $itemOffered):void
+	{
+		if (is_array($itemOffered)) {
+			$itemOffered = $this->mixedArray('itemOffered', $itemOffered, [
+				'JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork',
+				'JVBase\managers\SEO\render\Thing\Event\Event',
+				'JVBase\managers\SEO\render\Thing\Intangible\AggregateOffer',
+				'JVBase\managers\SEO\render\Thing\Intangible\MenuItem',
+				'JVBase\managers\SEO\render\Thing\Intangible\Service',
+				'JVBase\managers\SEO\render\Thing\Product\Product'
+			]);
+		}
+		$this->itemOffered = $itemOffered;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/itemReviewedTrait.php b/inc/managers/SEO/render/Traits/_Properties/itemReviewedTrait.php
new file mode 100644
index 0000000..e35f986
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/itemReviewedTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait itemReviewedTrait {
+	/**
+	 * @var Thing The item that is being reviewed/rated.
+	 */
+	protected Thing $itemReviewed;
+
+	public function getItemReviewed():?Thing
+	{
+		return $this->itemReviewed??null;
+	}
+	public function setItemReviewed(Thing $itemReviewed):void
+	{
+		$this->itemReviewed = $itemReviewed;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/itemTrait.php b/inc/managers/SEO/render/Traits/_Properties/itemTrait.php
new file mode 100644
index 0000000..ac0864a
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/itemTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait itemTrait {
+	/**
+	 * @var Thing An entity represented by an entry in a list or data feed (e.g. an 'artist' in a list of 'artists').
+	 */
+	protected Thing $item;
+
+	public function getItem():?Thing
+	{
+		return $this->item??null;
+	}
+	public function setItem(Thing $item):void
+	{
+		$this->item = $item;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/jobTitleTrait.php b/inc/managers/SEO/render/Traits/_Properties/jobTitleTrait.php
new file mode 100644
index 0000000..5233104
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/jobTitleTrait.php
@@ -0,0 +1,31 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\DefinedTerm;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait jobTitleTrait {
+	use arrayHelper;
+	/**
+	 * @var DefinedTerm|string|array The job title of the person (for example, Financial Manager).
+	 */
+	protected DefinedTerm|string|array $jobTitle;
+
+	public function getJobTitle():DefinedTerm|string|array|null
+	{
+		return $this->jobTitle??null;
+	}
+	public function setJobTitle(DefinedTerm|string|array $jobTitle):void
+	{
+		if (is_array($jobTitle)) {
+			$jobTitle = $this->mixedArray('jobTitle', $jobTitle, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\Intangible\DefinedTerm'
+			]);
+		}
+		$this->jobTitle = $jobTitle;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/keywordsTrait.php b/inc/managers/SEO/render/Traits/_Properties/keywordsTrait.php
new file mode 100644
index 0000000..658ffff
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/keywordsTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\DefinedTerm;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait keywordsTrait {
+	use arrayHelper;
+	/**
+	 * @var DefinedTerm|string|array Keywords or tags used to describe some item. Multiple textual entries in a keywords list are typically delimited by commas, or by repeating the property.
+	 */
+	protected DefinedTerm|string|array $keywords;
+
+	public function getKeywords():DefinedTerm|string|array|null
+	{
+		return $this->keywords??null;
+	}
+	public function setKeywords(DefinedTerm|string|array $keywords):void
+	{
+		if (is_array($keywords)){
+			$keywords = $this->mixedArray('keywords', $keywords, ['string', 'JVBase\managers\SEO\render\Thing\Intangible\DefinedTerm']);
+		}
+		$this->keywords = $keywords;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/knowsAboutTrait.php b/inc/managers/SEO/render/Traits/_Properties/knowsAboutTrait.php
new file mode 100644
index 0000000..f45c1a0
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/knowsAboutTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait knowsAboutTrait {
+	use arrayHelper;
+	/**
+	 * @var Thing|string|array Of a Person, and less typically of an Organization, to indicate a topic that is known about - suggesting possible expertise but not implying it. We do not distinguish skill levels here, or relate this to educational content, events, objectives or JobPosting descriptions.
+	 */
+	protected Thing|string|array $knowsAbout;
+
+	public function getKnowsAbout():Thing|string|array|null
+	{
+		return $this->knowsAbout??null;
+	}
+	public function setKnowsAbout(Thing|string|array $knowsAbout):void
+	{
+		if (is_array($knowsAbout)){
+			$knowsAbout = $this->mixedArray('knowsAbout', $knowsAbout, ['string', 'JVBase\managers\SEO\render\Thing\Thing']);
+		}
+		$this->knowsAbout = $knowsAbout;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/knowsLanguageTrait.php b/inc/managers/SEO/render/Traits/_Properties/knowsLanguageTrait.php
new file mode 100644
index 0000000..38ea199
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/knowsLanguageTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Language;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait knowsLanguageTrait {
+	use arrayHelper;
+	/**
+	 * @var Language|string|array Of a Person, and less typically of an Organization, to indicate a known language. We do not distinguish skill levels or reading/writing/speaking/signing here. Use language codes from the IETF BCP 47 standard.
+	 */
+	protected Language|string|array $knowsLanguage;
+
+	public function getKnowsLanguage():Language|string|array|null
+	{
+		return $this->knowsLanguage??null;
+	}
+	public function setKnowsLanguage(Language|string|array $knowsLanguage):void
+	{
+		if (is_array($knowsLanguage)){
+			$knowsLanguage = $this->mixedArray('knowsLanguage', $knowsLanguage, ['string', 'JVBase\managers\SEO\render\Thing\Intangible\Language']);
+		}
+		$this->knowsLanguage = $knowsLanguage;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/knowsTrait.php b/inc/managers/SEO/render/Traits/_Properties/knowsTrait.php
new file mode 100644
index 0000000..0ff23d7
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/knowsTrait.php
@@ -0,0 +1,31 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait knowsTrait {
+	use arrayHelper;
+	/**
+	 * @var Person|string|array The most generic bi-directional social/work relation.
+	 */
+	protected Person|string|array $knows;
+
+	public function getKnows():Person|string|array|null
+	{
+		return $this->knows??null;
+	}
+	public function setKnows(Person|string|array $knows):void
+	{
+		if (is_array($knows)) {
+			$knows = $this->mixedArray('knows', $knows, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\Person\Person'
+			]);
+		}
+		$this->knows = $knows;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/languageTrait.php b/inc/managers/SEO/render/Traits/_Properties/languageTrait.php
new file mode 100644
index 0000000..100d46d
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/languageTrait.php
@@ -0,0 +1,31 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Language;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait languageTrait {
+	use arrayHelper;
+	/**
+	 * @var Language|string|array The language of the content or performance or used in an action. Please use one of the language codes from the IETF BCP 47 standard. See also availableLanguage. Supersedes language.
+	 */
+	protected Language|string|array $language;
+
+	public function getLanguage():Language|string|array|null
+	{
+		return $this->language??null;
+	}
+	public function setLanguage(Language|string|array $language):void
+	{
+		if (is_array($language)){
+			$language = $this->mixedArray('language', $language, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\Intangible\Language'
+			]);
+		}
+		$this->language = $language;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/lastReviewedTrait.php b/inc/managers/SEO/render/Traits/_Properties/lastReviewedTrait.php
new file mode 100644
index 0000000..4b19408
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/lastReviewedTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\DataType\Date;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait lastReviewedTrait {
+	/**
+	 * @var Date Date on which the content on this web page was last reviewed for accuracy and/or completeness.
+	 */
+	protected Date $lastReviewed;
+
+	public function getLastReviewed():?Date
+	{
+		return $this->lastReviewed??null;
+	}
+	public function setLastReviewed(Date $lastReviewed):void
+	{
+		$this->lastReviewed = $lastReviewed;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/latitudeTrait.php b/inc/managers/SEO/render/Traits/_Properties/latitudeTrait.php
new file mode 100644
index 0000000..8ff715e
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/latitudeTrait.php
@@ -0,0 +1,30 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait latitudeTrait {
+	/**
+	 * @var string|float The latitude of a location. For example 37.42242 (WGS 84).
+	 */
+	protected string|float $latitude;
+
+	public function getLatitude():string|float|null
+	{
+		return $this->latitude??null;
+	}
+	public function setLatitude(string|float $latitude):void
+	{
+		$this->latitude = $latitude;
+	}
+
+	public function getLatitudeFieldConfig():array
+	{
+		return [
+			'type'	=> 'text',
+			'label'	=> 'Latitude',
+			'hint'	=> 'The latitude of a location. For example 37.42242'
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/legalAddressTrait.php b/inc/managers/SEO/render/Traits/_Properties/legalAddressTrait.php
new file mode 100644
index 0000000..b9358b8
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/legalAddressTrait.php
@@ -0,0 +1,68 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\ContactPoint\PostalAddress;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait legalAddressTrait {
+	/**
+	 * @var PostalAddress The legal address of an organization which acts as the officially registered address used for legal and tax purposes. The legal address can be different from the place of operations of a business and other addresses can be part of an organization.
+	 */
+	protected PostalAddress $legalAddress;
+
+	public function getLegalAddress():?PostalAddress
+	{
+		return $this->legalAddress??null;
+	}
+	public function setLegalAddress(PostalAddress $legalAddress):void
+	{
+		$this->legalAddress = $legalAddress;
+	}
+
+	public function getLegalAddressFieldConfig():array
+	{
+		return [
+			'type'	=> 'group',
+			'label'	=> 'Address',
+			'wrap'	=> 'details',
+			'fields'	=> [
+				'streetAddress'	=> [
+					'type' => 'text',
+					'label'=> 'Street Address',
+					'hint'	=> 'The street address. For example, "6551 111 St NW"'
+				],
+				'extendedAddress'	=> [
+					'type' => 'text',
+					'label'=> 'Extended Address',
+					'hint'	=> 'An address extension such as an apartment number, C/O or alternative name.'
+				],
+				'postOfficeBoxNumber'	=> [
+					'type' => 'text',
+					'label' => 'PO Box Number',
+				],
+				'addressLocality'	=> [
+					'type'	=> 'text',
+					'label'	=> 'Address Locality',
+					'hint'	=> 'The locality in which the street address is, and which is in the region. For example, "Park Allen".'
+				],
+				'addressRegion'	=> [
+					'type'	=> 'text',
+					'label'	=> 'Address Region (Province)',
+				],
+				'postalCode'	=> [
+					'type'	=> 'text',
+					'label'	=> 'Postal Code',
+					'hint'	=> 'The postal code. For example, T6H 4R5.'
+				],
+				'addressCountry'	=> [
+					'type'	=> 'text',
+					'label'	=> 'Country',
+					'hint'	=> 'The address country. For example, "CA".',
+					'default' => 'CA'
+				]
+			]
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/legalNameTrait.php b/inc/managers/SEO/render/Traits/_Properties/legalNameTrait.php
new file mode 100644
index 0000000..691e92b
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/legalNameTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait legalNameTrait {
+	/**
+	 * @var string The official name of the organization, e.g. the registered company name.
+	 */
+	protected string $legalName;
+
+	public function getLegalName():?string
+	{
+		return $this->legalName??null;
+	}
+	public function setLegalName(string $legalName):void
+	{
+		$this->legalName = $legalName;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/legalRepresentativeTrait.php b/inc/managers/SEO/render/Traits/_Properties/legalRepresentativeTrait.php
new file mode 100644
index 0000000..666c97b
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/legalRepresentativeTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait legalRepresentativeTrait {
+	use arrayHelper;
+	/**
+	 * @var Person|array One or multiple persons who represent this organization legally such as CEO or sole administrator.
+	 */
+	protected Person|array $legalRepresentative;
+
+	public function getLegalRepresentative():Person|array|null
+	{
+		return $this->legalRepresentative??null;
+	}
+	public function setLegalRepresentative(Person|array $legalRepresentative):void
+	{
+		if (is_array($legalRepresentative)){
+			$legalRepresentative = $this->classArray('legalRepresentative', $legalRepresentative, 'JVBase\managers\SEO\render\Thing\Person\Person');
+		}
+		$this->legalRepresentative = $legalRepresentative;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/licenseTrait.php b/inc/managers/SEO/render/Traits/_Properties/licenseTrait.php
new file mode 100644
index 0000000..86ba694
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/licenseTrait.php
@@ -0,0 +1,27 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait licenseTrait {
+	/**
+	 * @var CreativeWork|string A license document that applies to this content, typically indicated by URL.
+	 */
+	protected CreativeWork|string $license;
+
+	public function getLicense():CreativeWork|string|null
+	{
+		return $this->license??null;
+	}
+	public function setLicense(CreativeWork|string $license):void
+	{
+		if (is_string($license) && !filter_var($license, FILTER_VALIDATE_URL)) {
+			error_log('[SEO] License is expected to be a valid URL');
+			return;
+		}
+		$this->license = $license;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/lifeEventTrait.php b/inc/managers/SEO/render/Traits/_Properties/lifeEventTrait.php
new file mode 100644
index 0000000..40b3379
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/lifeEventTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Event\Event;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait lifeEventTrait {
+	use arrayHelper;
+	/**
+	 * @var Event|array A life event like baptism, communions, Bar Mitzvahs, Aqiqah, Namakarana, Miyamairi, burial, ....
+	 */
+	protected Event|array $lifeEvent;
+
+	public function getLifeEvent():Event|array|null
+	{
+		return $this->lifeEvent??null;
+	}
+	public function setLifeEvent(Event|array $lifeEvent):void
+	{
+		if(is_array($lifeEvent)){
+			$lifeEvent = $this->classArray('lifeEvent', $lifeEvent, 'JVBase\managers\SEO\render\Thing\Event\Event');
+		}
+		$this->lifeEvent = $lifeEvent;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/lineTrait.php b/inc/managers/SEO/render/Traits/_Properties/lineTrait.php
new file mode 100644
index 0000000..292ffcb
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/lineTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait lineTrait {
+	/**
+	 * @var string A line is a point-to-point path consisting of two or more points. A line is expressed as a series of two or more point objects separated by space.
+	 */
+	protected string $line;
+
+	public function getLine():?string
+	{
+		return $this->line??null;
+	}
+	public function setLine(string $line):void
+	{
+		$this->line = $line;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/locationCreatedTrait.php b/inc/managers/SEO/render/Traits/_Properties/locationCreatedTrait.php
new file mode 100644
index 0000000..a9d5163
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/locationCreatedTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait locationCreatedTrait {
+	use arrayHelper;
+	/**
+	 * @var Place|array The location where the CreativeWork was created, which may not be the same as the location depicted in the CreativeWork.
+	 */
+	protected Place|array $locationCreated;
+
+	public function getLocationCreated():Place|array|null
+	{
+		return $this->locationCreated??null;
+	}
+	public function setLocationCreated(Place|array $locationCreated):void
+	{
+		if (is_array($locationCreated)) {
+			$locationCreated = $this->classArray('locationCreated', $locationCreated, 'JVBase\managers\SEO\render\Thing\Place\Place');
+		}
+		$this->locationCreated = $locationCreated;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/locationTrait.php b/inc/managers/SEO/render/Traits/_Properties/locationTrait.php
new file mode 100644
index 0000000..329b5b7
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/locationTrait.php
@@ -0,0 +1,81 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\ContactPoint\PostalAddress;
+use JVBase\managers\SEO\render\Thing\Intangible\VirtualLocation;
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait locationTrait {
+	use arrayHelper;
+	/**
+	 * @var Place|PostalAddress|VirtualLocation|string|array The location of, for example, where an event is happening, where an organization is located, or where an action takes place.
+	 */
+	protected Place|PostalAddress|VirtualLocation|string|array $location;
+
+	public function getLocation():Place|PostalAddress|VirtualLocation|string|array|null
+	{
+		return $this->location??null;
+	}
+	public function setLocation(Place|PostalAddress|VirtualLocation|string|array $location):void
+	{
+		if (is_array($location)){
+			$location = $this->mixedArray('location', $location, [
+				'JVBase\managers\SEO\render\Thing\Intangible\ContactPoint\PostalAddress',
+				'JVBase\managers\SEO\render\Thing\Intangible\VirtualLocation',
+				'JVBase\managers\SEO\render\Thing\Place\Place',
+				'string'
+			]);
+		}
+		$this->location = $location;
+	}
+
+
+	public function getLocationFieldConfig():array
+	{
+		return [
+			'type'	=> 'group',
+			'wrap'	=> 'details',
+			'label'	=> 'Address',
+			'fields'	=> [
+				'streetAddress'	=> [
+					'type' => 'text',
+					'label'=> 'Street Address',
+					'hint'	=> 'The street address. For example, "6551 111 St NW"'
+				],
+				'extendedAddress'	=> [
+					'type' => 'text',
+					'label'=> 'Extended Address',
+					'hint'	=> 'An address extension such as an apartment number, C/O or alternative name.'
+				],
+				'postOfficeBoxNumber'	=> [
+					'type' => 'text',
+					'label' => 'PO Box Number',
+				],
+				'addressLocality'	=> [
+					'type'	=> 'text',
+					'label'	=> 'Address Locality',
+					'hint'	=> 'The locality in which the street address is, and which is in the region. For example, "Park Allen".'
+				],
+				'addressRegion'	=> [
+					'type'	=> 'text',
+					'label'	=> 'Address Region (Province)',
+				],
+				'postalCode'	=> [
+					'type'	=> 'text',
+					'label'	=> 'Postal Code',
+					'hint'	=> 'The postal code. For example, T6H 4R5.'
+				],
+				'addressCountry'	=> [
+					'type'	=> 'text',
+					'label'	=> 'Country',
+					'hint'	=> 'The address country. For example, "CA".',
+					'default' => 'CA'
+				]
+			]
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/logoTrait.php b/inc/managers/SEO/render/Traits/_Properties/logoTrait.php
new file mode 100644
index 0000000..b38a0b8
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/logoTrait.php
@@ -0,0 +1,33 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\MediaObject\ImageObject;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait logoTrait {
+	/**
+	 * @var string|ImageObject Can be a URL, or a fully described ImageObject
+	 */
+	protected string|ImageObject $logo;
+
+	public function getLogo():?string
+	{
+		return $this->logo??null;
+	}
+	public function setLogo(string|ImageObject $logo):void
+	{
+		$this->logo = $logo;
+	}
+
+	public function getLogoFieldConfig():array
+	{
+		return [
+			'type'	=> 'upload',
+			'multiple'	=> false,
+			'label'	=> 'Logo',
+			'hint'	=> 'An associated logo.'
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/longitudeTrait.php b/inc/managers/SEO/render/Traits/_Properties/longitudeTrait.php
new file mode 100644
index 0000000..f94abdb
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/longitudeTrait.php
@@ -0,0 +1,30 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait longitudeTrait {
+	/**
+	 * @var string|float The longitude of a location. For example 37.42242 (WGS 84).
+	 */
+	protected string|float $longitude;
+
+	public function getLongitude():string|float|null
+	{
+		return $this->longitude??null;
+	}
+	public function setLongitude(string|float $longitude):void
+	{
+		$this->longitude = $longitude;
+	}
+
+	public function getLongitudeFieldConfig():array
+	{
+		return [
+			'type'	=> 'text',
+			'label'	=> 'Longitude',
+			'hint'	=> 'The longitude of a location. For example -122.08585'
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/lowPriceTrait.php b/inc/managers/SEO/render/Traits/_Properties/lowPriceTrait.php
new file mode 100644
index 0000000..af6b49a
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/lowPriceTrait.php
@@ -0,0 +1,26 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait lowPriceTrait {
+	/**
+	 * @var int|float The lowest price of all offers available.
+	 *
+	 * Usage guidelines:
+	 *
+	 * Use values from 0123456789 (Unicode 'DIGIT ZERO' (U+0030) to 'DIGIT NINE' (U+0039)) rather than superficially similar Unicode symbols.
+	 * Use '.' (Unicode 'FULL STOP' (U+002E)) rather than ',' to indicate a decimal point. Avoid using these symbols as a readability separator.
+	 */
+	protected int|float $lowPrice;
+
+	public function getLowPrice():int|float|null
+	{
+		return $this->lowPrice??null;
+	}
+	public function setLowPrice(int|float $lowPrice):void
+	{
+		$this->lowPrice = $lowPrice;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/mainEntityOfPageTrait.php b/inc/managers/SEO/render/Traits/_Properties/mainEntityOfPageTrait.php
new file mode 100644
index 0000000..48a125b
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/mainEntityOfPageTrait.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait mainEntityOfPageTrait {
+	/**
+	 * @var CreativeWork|string Indicates page (or other CreativeWork) for which this thing is the main entity being described.
+	 * Inverse property of mainEntity
+	 */
+	protected CreativeWork|string $mainEntityOfPage;
+
+	public function getMainEntityOfPage():CreativeWork|string|null
+	{
+		return $this->mainEntityOfPage??null;
+	}
+	public function setMainEntityOfPage(CreativeWork|string $mainEntityOfPage):void
+	{
+		$this->mainEntityOfPage = $mainEntityOfPage;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/mainEntityTrait.php b/inc/managers/SEO/render/Traits/_Properties/mainEntityTrait.php
new file mode 100644
index 0000000..9246787
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/mainEntityTrait.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait mainEntityTrait {
+	/**
+	 * @var Thing Indicates the primary entity described in some page or other CreativeWork.
+	 * Inverse property: mainEntityOfPage
+	 */
+	protected Thing $mainEntity;
+
+	public function getMainEntity():?Thing
+	{
+		return $this->mainEntity??null;
+	}
+	public function setMainEntity(Thing $mainEntity):void
+	{
+		$this->mainEntity = $mainEntity;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/maintainerTrait.php b/inc/managers/SEO/render/Traits/_Properties/maintainerTrait.php
new file mode 100644
index 0000000..cbae8ba
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/maintainerTrait.php
@@ -0,0 +1,32 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait maintainerTrait {
+	use arrayHelper;
+	/**
+	 * @var Organization|Person|array A maintainer of a Dataset, software package (SoftwareApplication), or other Project. A maintainer is a Person or Organization that manages contributions to, and/or publication of, some (typically complex) artifact. It is common for distributions of software and data to be based on "upstream" sources. When maintainer is applied to a specific version of something e.g. a particular version or packaging of a Dataset, it is always possible that the upstream source has a different maintainer. The isBasedOn property can be used to indicate such relationships between datasets to make the different maintenance roles clear. Similarly in the case of software, a package may have dedicated maintainers working on integration into software distributions such as Ubuntu, as well as upstream maintainers of the underlying work.
+	 */
+	protected Organization|Person|array $maintainer;
+
+	public function getMaintainer():Organization|Person|array|null
+	{
+		return $this->maintainer??null;
+	}
+	public function setMaintainer(Organization|Person|array $maintainer):void
+	{
+		if (is_array($maintainer)) {
+			$maintainer = $this->mixedArray('maintainer', $maintainer, [
+				'JVBase\managers\SEO\render\Thing\Organization\Organization',
+				'JVBase\managers\SEO\render\Thing\Person\Person'
+			]);
+		}
+		$this->maintainer = $maintainer;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/makesOfferTrait.php b/inc/managers/SEO/render/Traits/_Properties/makesOfferTrait.php
new file mode 100644
index 0000000..9ef3f69
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/makesOfferTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Offer;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait makesOfferTrait {
+	use arrayHelper;
+	/**
+	 * @var Offer|array A makesOffer of the item
+	 */
+	protected Offer|array $makesOffer;
+
+	public function getMakesOffer():Offer|array|null
+	{
+		return $this->makesOffer??null;
+	}
+	public function setMakesOffer(Offer|array $makesOffer):void
+	{
+		if (is_array($makesOffer)) {
+			$makesOffer = $this->classArray('makesOffer', $makesOffer, 'JVBase\managers\SEO\render\Thing\Intangible\Offer');
+		}
+		$this->makesOffer = $makesOffer;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/manufacturerTrait.php b/inc/managers/SEO/render/Traits/_Properties/manufacturerTrait.php
new file mode 100644
index 0000000..9c2d846
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/manufacturerTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait manufacturerTrait {
+	/**
+	 * @var Organization The manufacturer of the product.
+	 */
+	protected Organization $manufacturer;
+
+	public function getManufacturer():?Organization
+	{
+		return $this->manufacturer??null;
+	}
+	public function setManufacturer(Organization $manufacturer):void
+	{
+		$this->manufacturer = $manufacturer;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/materialExtentTrait.php b/inc/managers/SEO/render/Traits/_Properties/materialExtentTrait.php
new file mode 100644
index 0000000..f0457ff
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/materialExtentTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait materialExtentTrait {
+	/**
+	 * @var QuantitativeValue|string The quantity of the materials being described or an expression of the physical space they occupy.
+	 */
+	protected QuantitativeValue|string $materialExtent;
+
+	public function getMaterialExtent():QuantitativeValue|string|null
+	{
+		return $this->materialExtent??null;
+	}
+	public function setMaterialExtent(QuantitativeValue|string $materialExtent):void
+	{
+		$this->materialExtent = $materialExtent;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/materialTrait.php b/inc/managers/SEO/render/Traits/_Properties/materialTrait.php
new file mode 100644
index 0000000..93e89a6
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/materialTrait.php
@@ -0,0 +1,31 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Product\Product;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait materialTrait {
+	use arrayHelper;
+	/**
+	 * @var Product|string|array A material that something is made from, e.g. leather, wool, cotton, paper.
+	 */
+	protected Product|string|array $material;
+
+	public function getMaterial():Product|string|array|null
+	{
+		return $this->material??null;
+	}
+	public function setMaterial(Product|string|array $material):void
+	{
+		if (is_array($material)) {
+			$material = $this->mixedArray('material', $material, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\Product\Product'
+			]);
+		}
+		$this->material = $material;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/maxPriceTrait.php b/inc/managers/SEO/render/Traits/_Properties/maxPriceTrait.php
new file mode 100644
index 0000000..acef04f
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/maxPriceTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait maxPriceTrait {
+	/**
+	 * @var int|float The highest price if the price is a range.
+	 */
+	protected int|float $maxPrice;
+
+	public function getMaxPrice():int|float|null
+	{
+		return $this->maxPrice??null;
+	}
+	public function setMaxPrice(int|float $maxPrice):void
+	{
+		$this->maxPrice = $maxPrice;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/maxValueTrait.php b/inc/managers/SEO/render/Traits/_Properties/maxValueTrait.php
new file mode 100644
index 0000000..ba95344
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/maxValueTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait maxValueTrait {
+	/**
+	 * @var int|float|string The upper value of some characteristic or property
+	 */
+	protected int|float|string $maxValue;
+
+	public function getMaxValue():int|float|string|null
+	{
+		return $this->maxValue??null;
+	}
+	public function setMaxValue(int|float|string $maxValue):void
+	{
+		$this->maxValue = $maxValue;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/maximumAttendeeCapacityTrait.php b/inc/managers/SEO/render/Traits/_Properties/maximumAttendeeCapacityTrait.php
new file mode 100644
index 0000000..b6337c8
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/maximumAttendeeCapacityTrait.php
@@ -0,0 +1,29 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait maximumAttendeeCapacityTrait {
+	/**
+	 * @var int The total number of individuals that may attend an event or venue.
+	 */
+	protected int $maximumAttendeeCapacity;
+
+	public function getMaximumAttendeeCapacity():?int
+	{
+		return $this->maximumAttendeeCapacity??null;
+	}
+	public function setMaximumAttendeeCapacity(int $maximumAttendeeCapacity):void
+	{
+		$this->maximumAttendeeCapacity = $maximumAttendeeCapacity;
+	}
+
+	public function getMaximumAttendeeCapacityFieldConfig():array
+	{
+		return [
+			'type'	=> 'number',
+			'label'	=> 'Maximum Attendee Capacity',
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/maximumPhysicalAttendeeCapacityTrait.php b/inc/managers/SEO/render/Traits/_Properties/maximumPhysicalAttendeeCapacityTrait.php
new file mode 100644
index 0000000..772a8d6
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/maximumPhysicalAttendeeCapacityTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait maximumPhysicalAttendeeCapacityTrait {
+	/**
+	 * @var int The maximum physical attendee capacity of an Event whose eventAttendanceMode is OfflineEventAttendanceMode (or the offline aspects, in the case of a MixedEventAttendanceMode).
+	 */
+	protected int $maximumPhysicalAttendeeCapacity;
+
+	public function getMaximumPhysicalAttendeeCapacity():?int
+	{
+		return $this->maximumPhysicalAttendeeCapacity??null;
+	}
+	public function setMaximumPhysicalAttendeeCapacity(int $maximumPhysicalAttendeeCapacity):void
+	{
+		$this->maximumPhysicalAttendeeCapacity = $maximumPhysicalAttendeeCapacity;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/maximumVirtualAttendeeCapacityTrait.php b/inc/managers/SEO/render/Traits/_Properties/maximumVirtualAttendeeCapacityTrait.php
new file mode 100644
index 0000000..f2318fe
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/maximumVirtualAttendeeCapacityTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait maximumVirtualAttendeeCapacityTrait {
+	/**
+	 * @var int The maximum virtual attendee capacity of an Event whose eventAttendanceMode is OnlineEventAttendanceMode (or the online aspects, in the case of a MixedEventAttendanceMode).
+	 */
+	protected int $maximumVirtualAttendeeCapacity;
+
+	public function getMaximumVirtualAttendeeCapacity():?int
+	{
+		return $this->maximumVirtualAttendeeCapacity??null;
+	}
+	public function setMaximumVirtualAttendeeCapacity(int $maximumVirtualAttendeeCapacity):void
+	{
+		$this->maximumVirtualAttendeeCapacity = $maximumVirtualAttendeeCapacity;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/memberOfTrait.php b/inc/managers/SEO/render/Traits/_Properties/memberOfTrait.php
new file mode 100644
index 0000000..debb9d4
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/memberOfTrait.php
@@ -0,0 +1,30 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait memberOfTrait {
+	use arrayHelper;
+	/**
+	 * @var Organization|array An Organization (or ProgramMembership) to which this Person or Organization belongs.
+	 * Inverse property: member
+	 */
+	protected Organization|array $memberOf;
+
+	public function getMemberOf():Organization|array|null
+	{
+		return $this->memberOf??null;
+	}
+	public function setMemberOf(Organization|array $memberOf):void
+	{
+		if (is_array($memberOf))
+		{
+			$memberOf = $this->classArray('memberOf', $memberOf, 'JVBase\managers\SEO\render\Thing\Organization\Organization');
+		}
+		$this->memberOf = $memberOf;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/memberTrait.php b/inc/managers/SEO/render/Traits/_Properties/memberTrait.php
new file mode 100644
index 0000000..56bcf2c
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/memberTrait.php
@@ -0,0 +1,33 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait memberTrait {
+	use arrayHelper;
+	/**
+	 * @var Organization|Person|array A member of an Organization or a ProgramMembership. Organizations can be members of organizations; ProgramMembership is typically for individuals. Supersedes members, musicGroupMember.
+	 * Inverse property: memberOf
+	 */
+	protected Organization|Person|array $member;
+
+	public function getMember():Organization|Person|array|null
+	{
+		return $this->member??null;
+	}
+	public function setMember(Organization|Person|array $member):void
+	{
+		if (is_array($member)) {
+			$member = $this->mixedArray('member', $member, [
+				'JVBase\managers\SEO\render\Thing\Organization\Organization',
+				'JVBase\managers\SEO\render\Thing\Person\Person'
+			]);
+		}
+		$this->member = $member;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/membershipPointsEarnedTrait.php b/inc/managers/SEO/render/Traits/_Properties/membershipPointsEarnedTrait.php
new file mode 100644
index 0000000..ec5718e
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/membershipPointsEarnedTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait membershipPointsEarnedTrait {
+	/**
+	 * @var QuantitativeValue|int|float The number of membership points earned by the member. If necessary, the unitText can be used to express the units the points are issued in. (E.g. stars, miles, etc.)
+	 */
+	protected QuantitativeValue|int|float $membershipPointsEarned;
+
+	public function getMembershipPointsEarned():QuantitativeValue|int|float|null
+	{
+		return $this->membershipPointsEarned??null;
+	}
+	public function setMembershipPointsEarned(QuantitativeValue|int|float $membershipPointsEarned):void
+	{
+		$this->membershipPointsEarned = $membershipPointsEarned;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/mentionsTrait.php b/inc/managers/SEO/render/Traits/_Properties/mentionsTrait.php
new file mode 100644
index 0000000..ef7c16c
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/mentionsTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait mentionsTrait {
+	use arrayHelper;
+	/**
+	 * @var Thing|array Indicates that the CreativeWork contains a reference to, but is not necessarily about a concept.
+	 */
+	protected Thing|array $mentions;
+
+	public function getMentions():Thing|array|null
+	{
+		return $this->mentions??null;
+	}
+	public function setMentions(Thing|array $mentions):void
+	{
+		if (is_array($mentions)) {
+			$mentions = $this->classArray('mentions', $mentions, 'JVBase\managers\SEO\render\Thing\Thing');
+		}
+		$this->mentions = $mentions;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/menuAddOnTrait.php b/inc/managers/SEO/render/Traits/_Properties/menuAddOnTrait.php
new file mode 100644
index 0000000..2620251
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/menuAddOnTrait.php
@@ -0,0 +1,32 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\MenuSection;
+use JVBase\managers\SEO\render\Thing\Intangible\MenuItem;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait menuAddOnTrait {
+	use arrayHelper;
+	/**
+	 * @var MenuItem|MenuSection|array Additional menu item(s) such as a side dish of salad or side order of fries that can be added to this menu item. Additionally it can be a menu section containing allowed add-on menu items for this menu item.
+	 */
+	protected MenuItem|MenuSection|array $menuAddOn;
+
+	public function getMenuAddOn():MenuItem|MenuSection|array|null
+	{
+		return $this->menuAddOn??null;
+	}
+	public function setMenuAddOn(MenuItem|MenuSection|array $menuAddOn):void
+	{
+		if (is_array($menuAddOn)) {
+			$menuAddOn = $this->mixedArray('menuAddOn', $menuAddOn, [
+				'JVBase\managers\SEO\render\Thing\CreativeWork\MenuSection',
+				'JVBase\managers\SEO\render\Thing\Intangible\MenuItem'
+			]);
+		}
+		$this->menuAddOn = $menuAddOn;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/merchantReturnDaysTrait.php b/inc/managers/SEO/render/Traits/_Properties/merchantReturnDaysTrait.php
new file mode 100644
index 0000000..d69e214
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/merchantReturnDaysTrait.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\DataType\Date;
+use JVBase\managers\SEO\render\DataType\DateTime;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait merchantReturnDaysTrait {
+	/**
+	 * @var Date|DateTime|int Specifies either a fixed return date or the number of days (from the delivery date) that a product can be returned. Used when the returnPolicyCategory property is specified as MerchantReturnFiniteReturnWindow. Supersedes productReturnDays.
+	 */
+	protected Date|DateTime|int $merchantReturnDays;
+
+	public function getMerchantReturnDays():Date|DateTime|int|null
+	{
+		return $this->merchantReturnDays??null;
+	}
+	public function setMerchantReturnDays(Date|DateTime|int $merchantReturnDays):void
+	{
+		$this->merchantReturnDays = $merchantReturnDays;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/merchantReturnLinkTrait.php b/inc/managers/SEO/render/Traits/_Properties/merchantReturnLinkTrait.php
new file mode 100644
index 0000000..f8bb06d
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/merchantReturnLinkTrait.php
@@ -0,0 +1,25 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait merchantReturnLinkTrait {
+	/**
+	 * @var string Specifies a Web page or service by URL, for product returns. Supersedes productReturnLink.
+	 */
+	protected string $merchantReturnLink;
+
+	public function getMerchantReturnLink():?string
+	{
+		return $this->merchantReturnLink??null;
+	}
+	public function setMerchantReturnLink(string $merchantReturnLink):void
+	{
+		if (!filter_var($merchantReturnLink, FILTER_VALIDATE_URL)) {
+			error_log('[SEO]MerchantReturnLink is expected to be a valid url.');
+			return;
+		}
+		$this->merchantReturnLink = $merchantReturnLink;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/minPriceTrait.php b/inc/managers/SEO/render/Traits/_Properties/minPriceTrait.php
new file mode 100644
index 0000000..e9ae31b
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/minPriceTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait minPriceTrait {
+	/**
+	 * @var int|float The lowest price if the price is a range.
+	 */
+	protected int|float $minPrice;
+
+	public function getMinPrice():int|float|null
+	{
+		return $this->minPrice??null;
+	}
+	public function setMinPrice(int|float $minPrice):void
+	{
+		$this->minPrice = $minPrice;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/minValueTrait.php b/inc/managers/SEO/render/Traits/_Properties/minValueTrait.php
new file mode 100644
index 0000000..89488c5
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/minValueTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait minValueTrait {
+	/**
+	 * @var int|float|string The lower value of some characteristic or property
+	 */
+	protected int|float|string $minValue;
+
+	public function getMinValue():int|float|string|null
+	{
+		return $this->minValue??null;
+	}
+	public function setMinValue(int|float|string $minValue):void
+	{
+		$this->minValue = $minValue;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/mobileUrlTrait.php b/inc/managers/SEO/render/Traits/_Properties/mobileUrlTrait.php
new file mode 100644
index 0000000..25fe841
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/mobileUrlTrait.php
@@ -0,0 +1,27 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait mobileUrlTrait {
+	/**
+	 * @var string The mobileUrl property is provided for specific situations in which data consumers need to determine whether one of several provided URLs is a dedicated 'mobile site'.
+	 *
+	 * To discourage over-use, and reflecting intial usecases, the property is expected only on Product and Offer, rather than Thing. The general trend in web technology is towards responsive design in which content can be flexibly adapted to a wide range of browsing environments. Pages and sites referenced with the long-established url property should ideally also be usable on a wide variety of devices, including mobile phones. In most cases, it would be pointless and counter productive to attempt to update all url markup to use mobileUrl for more mobile-oriented pages. The property is intended for the case when items (primarily Product and Offer) have extra URLs hosted on an additional "mobile site" alongside the main one. It should not be taken as an endorsement of this publication style.
+	 */
+	protected string $url;
+
+	public function getUrl():?string
+	{
+		return $this->url??null;
+	}
+	public function setUrl(string $url):void
+	{
+		if (!filter_var($url, FILTER_VALIDATE_URL)) {
+			error_log('[SEO]Not a valid url: '.$url);
+			return;
+		}
+		$this->url = $url;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/monthsOfExperienceTrait.php b/inc/managers/SEO/render/Traits/_Properties/monthsOfExperienceTrait.php
new file mode 100644
index 0000000..4e988ad
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/monthsOfExperienceTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait monthsOfExperienceTrait {
+	/**
+	 * @var int Indicates the minimal number of months of experience required for a position.
+	 */
+	protected int $monthsOfExperience;
+
+	public function getMonthsOfExperience():?int
+	{
+		return $this->monthsOfExperience??null;
+	}
+	public function setMonthsOfExperience(int $monthsOfExperience):void
+	{
+		$this->monthsOfExperience = $monthsOfExperience;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/musicByTrait.php b/inc/managers/SEO/render/Traits/_Properties/musicByTrait.php
new file mode 100644
index 0000000..1e69372
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/musicByTrait.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Person\Person;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait musicByTrait {
+	/**
+	 * @var Person The composer of the soundtrack.
+	 * Note: can also be MusicGroup,  not included
+	 */
+	protected Person $musicBy;
+
+	public function getMusicBy():?Person
+	{
+		return $this->musicBy??null;
+	}
+	public function setMusicBy(Person $musicBy):void
+	{
+		$this->musicBy = $musicBy;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/naicsTrait.php b/inc/managers/SEO/render/Traits/_Properties/naicsTrait.php
new file mode 100644
index 0000000..604fc4f
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/naicsTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait naicsTrait {
+	/**
+	 * @var string The North American Industry Classification System (NAICS) code for a particular organization or business person.
+	 */
+	protected string $naics;
+
+	public function getNaics():?string
+	{
+		return $this->naics??null;
+	}
+	public function setNaics(string $naics):void
+	{
+		$this->naics = $naics;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/nameTrait.php b/inc/managers/SEO/render/Traits/_Properties/nameTrait.php
new file mode 100644
index 0000000..5448ad8
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/nameTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait nameTrait {
+	/**
+	 * @var string A name of the item
+	 */
+	protected string $name;
+
+	public function getName():?string
+	{
+		return $this->name??null;
+	}
+	public function setName(string $name):void
+	{
+		$this->name = $name;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/nationalityTrait.php b/inc/managers/SEO/render/Traits/_Properties/nationalityTrait.php
new file mode 100644
index 0000000..7d1e06a
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/nationalityTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Place\AdministrativeArea\Country;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait nationalityTrait {
+	/**
+	 * @var Country|string The place where the product was assembled.
+	 */
+	protected Country|string $nationality;
+
+	public function getNationality():Country|string|null
+	{
+		return $this->nationality??null;
+	}
+	public function setNationality(Country|string $nationality):void
+	{
+		$this->nationality = $nationality;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/negativeNotesTrait.php b/inc/managers/SEO/render/Traits/_Properties/negativeNotesTrait.php
new file mode 100644
index 0000000..47bf3be
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/negativeNotesTrait.php
@@ -0,0 +1,33 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\ItemList\ItemList;
+use JVBase\managers\SEO\render\Thing\Intangible\ListItem;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait negativeNotesTrait {
+	use arrayHelper;
+	/**
+	 * @var ItemList|ListItem|string|array Provides negative considerations regarding something, most typically in pro/con lists for reviews (alongside positiveNotes). For symmetry
+	 *
+	 * In the case of a Review, the property describes the itemReviewed from the perspective of the review; in the case of a Product, the product itself is being described. Since product negativeNotess tend to emphasise positive claims, it may be relatively unusual to find negativeNotes used in this way. Nevertheless for the sake of symmetry, negativeNotes can be used on Product.
+	 *
+	 * The property values can be expressed either as unstructured text (repeated as necessary), or if ordered, as a list (in which case the most negative is at the beginning of the list).
+	 */
+	protected ItemList|ListItem|string|array $negativeNotes;
+
+	public function getNegativeNotes():ItemList|ListItem|string|array|null
+	{
+		return $this->negativeNotes??null;
+	}
+	public function setNegativeNotes(ItemList|ListItem|string|array $negativeNotes):void
+	{
+		if (is_array($negativeNotes)) {
+			$negativeNotes = $this->stringArray('negativeNotes', $negativeNotes);
+		}
+		$this->negativeNotes = $negativeNotes;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/nextItemTrait.php b/inc/managers/SEO/render/Traits/_Properties/nextItemTrait.php
new file mode 100644
index 0000000..7bab001
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/nextItemTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\ListItem;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait nextItemTrait {
+	/**
+	 * @var ListItem A link to the ListItem that follows the current one.
+	 */
+	protected ListItem $nextItem;
+
+	public function getNextItem():?ListItem
+	{
+		return $this->nextItem??null;
+	}
+	public function setNextItem(ListItem $nextItem):void
+	{
+		$this->nextItem = $nextItem;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/numberOfEmployeesTrait.php b/inc/managers/SEO/render/Traits/_Properties/numberOfEmployeesTrait.php
new file mode 100644
index 0000000..b3f61db
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/numberOfEmployeesTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait numberOfEmployeesTrait {
+	/**
+	 * @var QuantitativeValue The number of employees in an organization, e.g. business.
+	 */
+	protected QuantitativeValue $numberOfEmployees;
+
+	public function getNumberOfEmployees():?QuantitativeValue
+	{
+		return $this->numberOfEmployees??null;
+	}
+	public function setNumberOfEmployees(QuantitativeValue $numberOfEmployees):void
+	{
+		$this->numberOfEmployees = $numberOfEmployees;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/numberOfItemsTrait.php b/inc/managers/SEO/render/Traits/_Properties/numberOfItemsTrait.php
new file mode 100644
index 0000000..d608535
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/numberOfItemsTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait numberOfItemsTrait {
+	/**
+	 * @var int The number of items in an ItemList. Note that some numberOfItemss might not fully describe all items in a list (e.g., multi-page pagination); in such cases, the numberOfItems would be for the entire list.
+	 */
+	protected int $numberOfItems;
+
+	public function getNumberOfItems():?int
+	{
+		return $this->numberOfItems??null;
+	}
+	public function setNumberOfItems(int $numberOfItems):void
+	{
+		$this->numberOfItems = $numberOfItems;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/nutritionTrait.php b/inc/managers/SEO/render/Traits/_Properties/nutritionTrait.php
new file mode 100644
index 0000000..3e38ddd
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/nutritionTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\NutritionalInformation;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait nutritionTrait {
+	/**
+	 * @var NutritionalInformation Nutrition information about the recipe or menu item.
+	 */
+	protected NutritionalInformation $nutrition;
+
+	public function getNutrition():?NutritionalInformation
+	{
+		return $this->nutrition??null;
+	}
+	public function setNutrition(NutritionalInformation $nutrition):void
+	{
+		$this->nutrition = $nutrition;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/objectTrait.php b/inc/managers/SEO/render/Traits/_Properties/objectTrait.php
new file mode 100644
index 0000000..3b75214
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/objectTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait objectTrait {
+	/**
+	 * @var Thing The object upon which the action is carried out, whose state is kept intact or changed. Also known as the semantic roles patient, affected or undergoer (which change their state) or theme (which doesn't). E.g. John read a book.
+	 */
+	protected Thing $object;
+
+	public function getObject():?Thing
+	{
+		return $this->object??null;
+	}
+	public function setObject(Thing $object):void
+	{
+		$this->object = $object;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/occupationLocationTrait.php b/inc/managers/SEO/render/Traits/_Properties/occupationLocationTrait.php
new file mode 100644
index 0000000..904362b
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/occupationLocationTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Place\AdministrativeArea\AdministrativeArea;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait occupationLocationTrait {
+	use arrayHelper;
+	/**
+	 * @var AdministrativeArea|array The region/country for which this occupational occupationLocation is appropriate. Note that educational requirements and qualifications can vary between jurisdictions.
+	 */
+	protected AdministrativeArea|array $occupationLocation;
+
+	public function getOccupationLocation():AdministrativeArea|array|null
+	{
+		return $this->occupationLocation??null;
+	}
+	public function setOccupationLocation(AdministrativeArea|array $occupationLocation):void
+	{
+		if (is_array($occupationLocation)) {
+			$occupationLocation = $this->classArray('occupationLocation', $occupationLocation, 'JVBase\managers\SEO\render\Thing\Place\AdministrativeArea\AdministrativeArea');
+		}
+		$this->occupationLocation = $occupationLocation;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/occupationalCategoryTrait.php b/inc/managers/SEO/render/Traits/_Properties/occupationalCategoryTrait.php
new file mode 100644
index 0000000..667e725
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/occupationalCategoryTrait.php
@@ -0,0 +1,36 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\CategoryCode;
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\PhysicalActivityCategory;
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait occupationalCategoryTrait {
+	use arrayHelper;
+	/**
+	 * @var CategoryCode|Thing|string|array A category describing the job, preferably using a term from a taxonomy such as BLS O*NET-SOC, ISCO-08 or similar, with the property repeated for each applicable value. Ideally the taxonomy should be identified, and both the textual label and formal code for the category should be provided.
+	 *
+	 * Note: for historical reasons, any textual label and formal code provided as a literal may be assumed to be from O*NET-SOC.
+	 */
+	protected CategoryCode|Thing|string|array $occupationalCategory;
+
+	public function getOccupationalCategory():CategoryCode|Thing|string|array|null
+	{
+		return $this->occupationalCategory??null;
+	}
+	public function setOccupationalCategory(CategoryCode|Thing|string|array $occupationalCategory):void
+	{
+		if (is_array($occupationalCategory)) {
+			$occupationalCategory = $this->mixedArray('occupationalCategory', $occupationalCategory, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\Thing',
+				'JVBase\managers\SEO\render\Thing\Intangible\CategoryCode'
+			]);
+		}
+		$this->occupationalCategory = $occupationalCategory;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/offerCountTrait.php b/inc/managers/SEO/render/Traits/_Properties/offerCountTrait.php
new file mode 100644
index 0000000..3b8f9ea
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/offerCountTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait offerCountTrait {
+	/**
+	 * @var int The number of offers for the product.
+	 */
+	protected int $offerCount;
+
+	public function getOfferCount():?int
+	{
+		return $this->offerCount??null;
+	}
+	public function setOfferCount(int $offerCount):void
+	{
+		$this->offerCount = $offerCount;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/offeredByTrait.php b/inc/managers/SEO/render/Traits/_Properties/offeredByTrait.php
new file mode 100644
index 0000000..6b5e83f
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/offeredByTrait.php
@@ -0,0 +1,33 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait offeredByTrait {
+	use arrayHelper;
+	/**
+	 * @var Person|Organization|array A pointer to the organization or person making the offer.
+	 * Inverse property: makesOffer
+	 */
+	protected Person|Organization|array $offeredBy;
+
+	public function getOfferedBy():Person|Organization|array|null
+	{
+		return $this->offeredBy??null;
+	}
+	public function setOfferedBy(Person|Organization|array $offeredBy):void
+	{
+		if (is_array($offeredBy)) {
+			$offeredBy = $this->mixedArray('offeredBy', $offeredBy,[
+				'JVBase\managers\SEO\render\Thing\Organization\Organization',
+				'JVBase\managers\SEO\render\Thing\Person\Person'
+			]);
+		}
+		$this->offeredBy = $offeredBy;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/offersTrait.php b/inc/managers/SEO/render/Traits/_Properties/offersTrait.php
new file mode 100644
index 0000000..c1dda00
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/offersTrait.php
@@ -0,0 +1,63 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Demand;
+use JVBase\managers\SEO\render\Thing\Intangible\Offer;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait offersTrait {
+	use arrayHelper;
+	/**
+	 * @var Demand|Offer|array An offer to provide this item—for example, an offer to sell a product, rent the DVD of a movie, perform a service, or give away tickets to an event. Use businessFunction to indicate the kind of transaction offered, i.e. sell, lease, etc. This property can also be used to describe a Demand. While this property is listed as expected on a number of common types, it can be used in others. In that case, using a second type, such as Product or a subtype of Product, can clarify the nature of the offer.
+	 * Inverse property: itemOffered
+	 */
+	protected Demand|Offer|array $offers;
+
+	public function getOffers():Demand|Offer|array|null
+	{
+		return $this->offers??null;
+	}
+	public function setOffers(Demand|Offer|array $offers):void
+	{
+		if (is_array($offers)) {
+			$offers = $this->mixedArray('offers', $offers,[
+				'JVBase\managers\SEO\render\Thing\Intangible\Demand',
+				'JVBase\managers\SEO\render\Thing\Intangible\Offer'
+			]);
+		}
+		$this->offers = $offers;
+	}
+	public function getOpeningHoursFieldConfig():array
+	{
+		return [
+			'type'	=> 'repeater',
+			'label'	=> 'Opening Hours',
+			'fields'	=> [
+				'dayOfWeek'	=> [
+					'type'	=> 'radio',
+					'label'	=> 'Day(s) of Week',
+					'options'	=> [
+						'Mo'	=> 'Monday',
+						'Tu'	=> 'Tuesday',
+						'We'	=> 'Wednesday',
+						'Th'	=> 'Thursday',
+						'Fr'	=> 'Friday',
+						'Sa'	=> 'Saturday',
+						'Su'	=> 'Sunday'
+					]
+				],
+				'opens'	=> [
+					'type'	=> 'time',
+					'label'	=> 'Opens At',
+				],
+				'closes'	=> [
+					'type'	=> 'time',
+					'label'	=> 'Closes At',
+				]
+			]
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/openingHoursSpecificationTrait.php b/inc/managers/SEO/render/Traits/_Properties/openingHoursSpecificationTrait.php
new file mode 100644
index 0000000..94f601c
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/openingHoursSpecificationTrait.php
@@ -0,0 +1,54 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\OpeningHoursSpecification;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait openingHoursSpecificationTrait {
+	/**
+	 * @var OpeningHoursSpecification The opening hours of a certain place.
+	 */
+	protected OpeningHoursSpecification $openingHoursSpecification;
+
+	public function getOpeningHoursSpecification():?OpeningHoursSpecification
+	{
+		return $this->openingHoursSpecification??null;
+	}
+	public function setOpeningHoursSpecification(OpeningHoursSpecification $openingHoursSpecification):void
+	{
+		$this->openingHoursSpecification = $openingHoursSpecification;
+	}
+
+	public function getOpeningHoursSpecificationFieldConfig():array
+	{
+		return [
+			'type'	=> 'repeater',
+			'label'	=> 'Opening Hours',
+			'fields'	=> [
+				'dayOfWeek'	=> [
+					'type'	=> 'radio',
+					'label'	=> 'Day(s) of Week',
+					'options'	=> [
+						'Mo'	=> 'Monday',
+						'Tu'	=> 'Tuesday',
+						'We'	=> 'Wednesday',
+						'Th'	=> 'Thursday',
+						'Fr'	=> 'Friday',
+						'Sa'	=> 'Saturday',
+						'Su'	=> 'Sunday'
+					]
+				],
+				'opens'	=> [
+					'type'	=> 'time',
+					'label'	=> 'Opens At',
+				],
+				'closes'	=> [
+					'type'	=> 'time',
+					'label'	=> 'Closes At',
+				]
+			]
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/openingHoursTrait.php b/inc/managers/SEO/render/Traits/_Properties/openingHoursTrait.php
new file mode 100644
index 0000000..f78f315
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/openingHoursTrait.php
@@ -0,0 +1,32 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait openingHoursTrait {
+	use arrayHelper;
+	/**
+	 * @var array|string The general opening hours for a business. Opening hours can be specified as a weekly time range, starting with days, then times per day. Multiple days can be listed with commas ',' separating each day. Day or time ranges are specified using a hyphen '-'.
+	 *
+	 * Days are specified using the following two-letter combinations: Mo, Tu, We, Th, Fr, Sa, Su.
+	 * Times are specified using 24:00 format. For example, 3pm is specified as 15:00, 10am as 10:00.
+	 * Here is an example: <time itemprop="openingHours" datetime="Tu,Th 16:00-20:00">Tuesdays and Thursdays 4-8pm</time>.
+	 * If a business is open 7 days a week, then it can be specified as <time itemprop="openingHours" datetime="Mo-Su">Monday through Sunday, all day</time>.
+	 */
+	protected array|string $openingHours;
+
+	public function getOpeningHours():array|string|null
+	{
+		return $this->openingHours??null;
+	}
+	public function setOpeningHours(array|string $openingHours):void
+	{
+		if (is_array($openingHours)) {
+			$openingHours = $this->stringArray('openingHours', $openingHours);
+		}
+		$this->openingHours = $openingHours;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/opensTrait.php b/inc/managers/SEO/render/Traits/_Properties/opensTrait.php
new file mode 100644
index 0000000..c27b70a
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/opensTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\DataType\Time;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait opensTrait {
+	/**
+	 * @var Time The opening hour of the place or service on the given day(s) of the week
+	 */
+	protected Time $opens;
+
+	public function getOpens():?Time
+	{
+		return $this->opens??null;
+	}
+	public function setOpens(Time $opens):void
+	{
+		$this->opens = $opens;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/organizerTrait.php b/inc/managers/SEO/render/Traits/_Properties/organizerTrait.php
new file mode 100644
index 0000000..2c5266d
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/organizerTrait.php
@@ -0,0 +1,32 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait organizerTrait {
+	use arrayHelper;
+	/**
+	 * @var Organization|Person|array An organizer of an Event.
+	 */
+	protected Organization|Person|array $organizer;
+
+	public function getOrganizer():Organization|Person|array|null
+	{
+		return $this->organizer??null;
+	}
+	public function setOrganizer(Organization|Person|array $organizer):void
+	{
+		if (is_array($organizer)) {
+			$organizer = $this->mixedArray('organizer', $organizer, [
+				'JVBase\managers\SEO\render\Thing\Organization\Organization',
+				'JVBase\managers\SEO\render\Thing\Person\Person'
+			]);
+		}
+		$this->organizer = $organizer;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/ownerTrait.php b/inc/managers/SEO/render/Traits/_Properties/ownerTrait.php
new file mode 100644
index 0000000..8f18c3b
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/ownerTrait.php
@@ -0,0 +1,56 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait ownerTrait {
+	use arrayHelper;
+	/**
+	 * @var Organization|Person|string|array A person or organization who owns this Thing.
+	 * Inverse property: owns
+	 */
+	protected Organization|Person|string|array $owner;
+
+	public function getOwner():Organization|Person|array|string|null
+	{
+		return $this->owner??null;
+	}
+	public function setOwner(Organization|Person|array|string $owner):void
+	{
+		if (is_array($owner)) {
+			$owner = $this->mixedArray('owner', $owner, ['JVBase\managers\SEO\render\Thing\Organization\Organization','JVBase\managers\SEO\render\Thing\Person\Person','string']);
+			if (empty($owner)){
+				return;
+			}
+		}
+		$this->owner = $owner;
+	}
+
+	public function getOwnerFieldConfig():array
+	{
+		return [
+			'type'	=> 'repeater',
+			'label'	=> 'Owner',
+			'hint'	=> 'A person or Organization that owns this Thing.',
+			'fields'	=> [
+				'name'	=> [
+					'type'	=> 'text',
+					'label'	=> 'Name',
+				],
+				'url'	=> [
+					'type'	=> 'url',
+					'label'	=> 'URL'
+				],
+				'email'	=> [
+					'type'	=> 'email',
+					'label'	=> 'Email'
+				]
+			]
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/ownershipFundInfoTrait.php b/inc/managers/SEO/render/Traits/_Properties/ownershipFundInfoTrait.php
new file mode 100644
index 0000000..f8f47c8
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/ownershipFundInfoTrait.php
@@ -0,0 +1,32 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+use JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\AboutPage;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait ownershipFundInfoTrait {
+	use arrayHelper;
+	/**
+	 * @var AboutPage|CreativeWork|string|array A ownershipFundingInfo of the item
+	 */
+	protected AboutPage|CreativeWork|string|array $ownershipFundingInfo;
+
+	public function getOwnershipFundingInfo():AboutPage|CreativeWork|string|array|null
+	{
+		return $this->ownershipFundingInfo??null;
+	}
+	public function setOwnershipFundingInfo(AboutPage|CreativeWork|string|array $ownershipFundingInfo):void
+	{
+		if (is_array($ownershipFundingInfo)) {
+			$ownershipFundingInfo = $this->mixedArray('ownershipFundingInfo', $ownershipFundingInfo, [
+				'JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork',
+				'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\AboutPage'
+			]);
+		}
+		$this->ownershipFundingInfo = $ownershipFundingInfo;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/ownsTrait.php b/inc/managers/SEO/render/Traits/_Properties/ownsTrait.php
new file mode 100644
index 0000000..fa864c7
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/ownsTrait.php
@@ -0,0 +1,48 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait ownsTrait {
+	use arrayHelper;
+	/**
+	 * @var Thing|array Things owned by the organization or person.
+	 * Inverse property: owner
+	 */
+	protected Thing|array $owns;
+
+	public function getOwns():Thing|array|null
+	{
+		return $this->owns??null;
+	}
+	public function setOwns(Thing|array $owns):void
+	{
+		if (is_array($owns)) {
+			$owns = $this->classArray('owns', $owns, 'JVBase\managers\SEO\render\Thing\Thing');
+		}
+		$this->owns = $owns;
+	}
+
+	public function getOwnsFieldConfig():array
+	{
+		return [
+			'type'	=> 'group',
+			'wrap'	=> 'details',
+			'label'	=> 'Owns',
+			'fields'	=> [
+				'name'	=> [
+					'type'	=> 'text',
+					'label'	=> 'Name of Thing.'
+				],
+				'url'	=> [
+					'type'	=> 'url',
+					'label'	=> 'URL of where to find Thing.'
+				]
+			]
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/parentOrganizationTrait.php b/inc/managers/SEO/render/Traits/_Properties/parentOrganizationTrait.php
new file mode 100644
index 0000000..c9fc5c2
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/parentOrganizationTrait.php
@@ -0,0 +1,48 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait parentOrganizationTrait {
+	use arrayHelper;
+	/**
+	 * @var Organization|array The larger organization that this organization is a subOrganization of, if any. Supersedes branchOf.
+	 * Inverse property: subOrganization
+	 */
+	protected Organization|array $parentOrganization;
+
+	public function getParentOrganization():Organization|array|null
+	{
+		return $this->parentOrganization??null;
+	}
+	public function setParentOrganization(Organization|array $parentOrganization):void
+	{
+		if (is_array($this->parentOrganization)) {
+			$parentOrganization = $this->classArray('parentOrganization', $parentOrganization, 'JVBase\managers\SEO\render\Thing\Organization\Organization');
+		}
+		$this->parentOrganization = $parentOrganization;
+	}
+
+	public function getParentOrganizationFieldConfig():array
+	{
+		return [
+			'type'	=> 'group',
+			'wrap'	=> 'details',
+			'label'	=> 'Parent Organization',
+			'fields'	=> [
+				'name'	=> [
+					'type'	=> 'text',
+					'label'	=> 'Name of Thing.'
+				],
+				'url'	=> [
+					'type'	=> 'url',
+					'label'	=> 'URL of where to find Thing.'
+				]
+			]
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/parentTrait.php b/inc/managers/SEO/render/Traits/_Properties/parentTrait.php
new file mode 100644
index 0000000..4b12975
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/parentTrait.php
@@ -0,0 +1,31 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait parentTrait {
+	use arrayHelper;
+	/**
+	 * @var Person|string|array A parent of this person. Supersedes parents.
+	 */
+	protected Person|string|array $parent;
+
+	public function getParent():Person|string|array|null
+	{
+		return $this->parent??null;
+	}
+	public function setParent(Person|string|array $parent):void
+	{
+		if (is_array($parent)) {
+			$parent = $this->mixedArray('parent', $parent, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\Person\Person'
+			]);
+		}
+		$this->parent = $parent;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/participantTrait.php b/inc/managers/SEO/render/Traits/_Properties/participantTrait.php
new file mode 100644
index 0000000..1c61ade
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/participantTrait.php
@@ -0,0 +1,33 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait participantTrait {
+	use arrayHelper;
+	/**
+	 * @var Organization|Person|string|array A person or organization who owns this Thing.
+	 * Inverse property: owns
+	 */
+	protected Organization|Person|string|array $participant;
+
+	public function getParticipant():Organization|Person|array|string|null
+	{
+		return $this->participant??null;
+	}
+	public function setParticipant(Organization|Person|array|string $participant):void
+	{
+		if (is_array($participant)) {
+			$participant = $this->mixedArray('participant', $participant, ['JVBase\managers\SEO\render\Thing\Organization\Organization','JVBase\managers\SEO\render\Thing\Person\Person','string']);
+			if (empty($participant)){
+				return;
+			}
+		}
+		$this->participant = $participant;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/partySizeTrait.php b/inc/managers/SEO/render/Traits/_Properties/partySizeTrait.php
new file mode 100644
index 0000000..b4230d7
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/partySizeTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait partySizeTrait {
+	/**
+	 * @var int Number of people the reservation should accommodate.
+	 */
+	protected int $partySize;
+
+	public function getPartySize():?int
+	{
+		return $this->partySize??null;
+	}
+	public function setPartySize(int $partySize):void
+	{
+		$this->partySize = $partySize;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/patternTrait.php b/inc/managers/SEO/render/Traits/_Properties/patternTrait.php
new file mode 100644
index 0000000..b08d6bf
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/patternTrait.php
@@ -0,0 +1,31 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\DefinedTerm;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait patternTrait {
+	use arrayHelper;
+	/**
+	 * @var DefinedTerm|string|array A pattern that something has, for example 'polka dot', 'striped', 'Canadian flag'. Values are typically expressed as text, although links to controlled value schemes are also supported.
+	 */
+	protected DefinedTerm|string|array $pattern;
+
+	public function getPattern():DefinedTerm|string|array|null
+	{
+		return $this->pattern??null;
+	}
+	public function setPattern(DefinedTerm|string|array $pattern):void
+	{
+		if (is_array($pattern)) {
+			$pattern = $this->mixedArray('pattern', $pattern, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\Intangible\DefinedTerm'
+			]);
+		}
+		$this->pattern = $pattern;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/paymentAcceptedTrait.php b/inc/managers/SEO/render/Traits/_Properties/paymentAcceptedTrait.php
new file mode 100644
index 0000000..fd7ced0
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/paymentAcceptedTrait.php
@@ -0,0 +1,36 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait paymentAcceptedTrait {
+	use arrayHelper;
+	/**
+	 * @var array|string Cash, Credit Card, Cryptocurrency, Local Exchange Tradings System, etc.
+	 */
+	protected array|string $paymentAccepted;
+
+	public function getPaymentAccepted():array|string|null
+	{
+		return $this->paymentAccepted??null;
+	}
+	public function setPaymentAccepted(array|string $paymentAccepted):void
+	{
+		if (is_array($paymentAccepted)) {
+			$paymentAccepted = $this->stringArray('paymentAccepted', $paymentAccepted);
+		}
+		$this->paymentAccepted = $paymentAccepted;
+	}
+
+	public function getPaymentAcceptedFieldConfig():array
+	{
+		return [
+			'type'	=> 'string',
+			'label'	=> 'Payment Accepted',
+			'hint'	=> 'A comma separated list of payment accepted, example: Cash, Credit Card, Cryptocurrency, Local Exchange Tradings System, etc.'
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/paymentMethodTrait.php b/inc/managers/SEO/render/Traits/_Properties/paymentMethodTrait.php
new file mode 100644
index 0000000..f01eb24
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/paymentMethodTrait.php
@@ -0,0 +1,75 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait paymentMethodTrait {
+	use arrayHelper;
+	/**
+	 * @var array|string The payment method(s) that are accepted in general by an organization, or for some specific demand or offer.
+	 */
+	protected array|string $paymentMethod;
+
+	protected array $mappedMethods = [
+		'banktransfer'	=> 'http://purl.org/goodrelations/v1#ByBankTransferInAdvance',
+		'invoice'		=> 'http://purl.org/goodrelations/v1#ByInvoice',
+		'cash'			=> 'http://purl.org/goodrelations/v1#Cash',
+		'check'			=> 'http://purl.org/goodrelations/v1#CheckInAdvance',
+		'cod'			=> 'http://purl.org/goodrelations/v1#COD',
+		'directdebit'	=> 'http://purl.org/goodrelations/v1#DirectDebit',
+		'google'		=> 'http://purl.org/goodrelations/v1#GoogleCheckout',
+		'paypal'		=> 'http://purl.org/goodrelations/v1#PayPal',
+		'payswarm'		=> 'http://purl.org/goodrelations/v1#PaySwarm',
+		'amex'			=> 'http://purl.org/goodrelations/v1#AmericanExpress',
+		'dinersclub'	=> 'http://purl.org/goodrelations/v1#DinersClub',
+		'discover'		=> 'http://purl.org/goodrelations/v1#Discover',
+		'mastercard'	=> 'http://purl.org/goodrelations/v1#MasterCard',
+		'visa'			=> 'http://purl.org/goodrelations/v1#VISA',
+		'jcb'			=> 'http://purl.org/goodrelations/v1#JCB'
+	];
+
+	public function getPaymentMethod():array|string|null
+	{
+		return $this->paymentMethod??null;
+	}
+	public function setPaymentMethod(array|string $paymentMethod):void
+	{
+		if (!is_array($paymentMethod)) {
+			$paymentMethod = [$paymentMethod];
+		}
+		$paymentMethod = $this->testAllowed('paymentMethod', $this->mappedMethods, $paymentMethod);
+		if (empty($paymentMethod)) {
+			return;
+		}
+		if (count($paymentMethod) === 1) {
+			$paymentMethod = $paymentMethod[0];
+		}
+		$this->paymentMethod = $paymentMethod;
+	}
+
+	public function formatPaymentMethod():array|null
+	{
+		if (!isset($this->paymentMethod)) {
+			return null;
+		}
+		if (is_array($this->paymentMethod)) {
+			return array_map(function($method) {
+				return ['@id' => $method];
+			}, $this->paymentMethod);
+		} else {
+			return ['@id' => $this->paymentMethod];
+		}
+	}
+
+	public function getPaymentMethodFieldConfig():array
+	{
+		return [
+			'type'	=> 'checkbox',
+			'label'	=> 'Payment Methods',
+			'options'	=> array_keys($this->mappedMethods)
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/performTimeTrait.php b/inc/managers/SEO/render/Traits/_Properties/performTimeTrait.php
new file mode 100644
index 0000000..bddc548
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/performTimeTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Quantity\Duration;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait performTimeTrait {
+	/**
+	 * @var Duration The length of time it takes to perform instructions or a direction (not including time to prepare the supplies), in ISO 8601 duration format.
+	 */
+	protected Duration $performTime;
+
+	public function getPerformTime():?Duration
+	{
+		return $this->performTime??null;
+	}
+	public function setPerformTime(Duration $performTime):void
+	{
+		$this->performTime = $performTime;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/performerInTrait.php b/inc/managers/SEO/render/Traits/_Properties/performerInTrait.php
new file mode 100644
index 0000000..2febd11
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/performerInTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Event\Event;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait performerInTrait {
+	use arrayHelper;
+	/**
+	 * @var Event|array Event(s) that this person is a performer or participant in.
+	 */
+	protected Event|array $performerIn;
+
+	public function getPerformerIn():Event|array|null
+	{
+		return $this->performerIn??null;
+	}
+	public function setPerformerIn(Event|array $performerIn):void
+	{
+		if (is_array($performerIn)) {
+			$performerIn = $this->classArray('performerIn', $performerIn, 'JVBase\managers\SEO\render\Thing\Event\Event');
+		}
+		$this->performerIn = $performerIn;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/performerTrait.php b/inc/managers/SEO/render/Traits/_Properties/performerTrait.php
new file mode 100644
index 0000000..fa16608
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/performerTrait.php
@@ -0,0 +1,26 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait performerTrait {
+	use arrayHelper;
+	/**
+	 * @var Organization|Person|array A performer at the event—for example, a presenter, musician, musical group or actor. Supersedes performers.
+	 */
+	protected Organization|Person|array $performer;
+
+	public function getPerformer():Organization|Person|array|null
+	{
+		return $this->performer??null;
+	}
+	public function setPerformer(Organization|Person|array $performer):void
+	{
+		$this->performer = $performer;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/photoTrait.php b/inc/managers/SEO/render/Traits/_Properties/photoTrait.php
new file mode 100644
index 0000000..883984a
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/photoTrait.php
@@ -0,0 +1,34 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\MediaObject\ImageObject;
+use JVBase\managers\SEO\render\Thing\CreativeWork\Photograph;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait photoTrait {
+	/**
+	 * @var ImageObject|Photograph A photograph of this place. Supersedes photos.
+	 */
+	protected ImageObject|Photograph $photo;
+
+	public function getPhoto():ImageObject|Photograph|null
+	{
+		return $this->photo??null;
+	}
+	public function setPhoto(ImageObject|Photograph $photo):void
+	{
+		$this->photo = $photo;
+	}
+
+	public function getPhotoFieldConfig():array
+	{
+		return [
+			'type'	=> 'upload',
+			'multiple'	=> false,
+			'label'	=> 'Photo',
+			'hint'	=> 'A photograph of this place.'
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/polygonTrait.php b/inc/managers/SEO/render/Traits/_Properties/polygonTrait.php
new file mode 100644
index 0000000..23b9ab3
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/polygonTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait polygonTrait {
+	/**
+	 * @var string A polygon is the area enclosed by a point-to-point path for which the starting and ending points are the same. A polygon is expressed as a series of four or more space delimited points where the first and final points are identical.
+	 */
+	protected string $polygon;
+
+	public function getPolygon():?string
+	{
+		return $this->polygon??null;
+	}
+	public function setPolygon(string $polygon):void
+	{
+		$this->polygon = $polygon;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/positionTrait.php b/inc/managers/SEO/render/Traits/_Properties/positionTrait.php
new file mode 100644
index 0000000..2c6a5f5
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/positionTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait positionTrait {
+	/**
+	 * @var int|string The position of an item in a series or sequence of items.
+	 */
+	protected int|string $position;
+
+	public function getPosition():int|string|null
+	{
+		return $this->position??null;
+	}
+	public function setPosition(int|string $position):void
+	{
+		$this->position = $position;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/positiveNotesTrait.php b/inc/managers/SEO/render/Traits/_Properties/positiveNotesTrait.php
new file mode 100644
index 0000000..a7d7b99
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/positiveNotesTrait.php
@@ -0,0 +1,33 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\ItemList\ItemList;
+use JVBase\managers\SEO\render\Thing\Intangible\ListItem;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait positiveNotesTrait {
+	use arrayHelper;
+	/**
+	 * @var ItemList|ListItem|string|array Provides positive considerations regarding something, most typically in pro/con lists for reviews (alongside positiveNotes). For symmetry
+	 *
+	 * In the case of a Review, the property describes the itemReviewed from the perspective of the review; in the case of a Product, the product itself is being described. Since product positiveNotess tend to emphasise positive claims, it may be relatively unusual to find positiveNotes used in this way. Nevertheless for the sake of symmetry, positiveNotes can be used on Product.
+	 *
+	 * The property values can be expressed either as unstructured text (repeated as necessary), or if ordered, as a list (in which case the most positive is at the beginning of the list).
+	 */
+	protected ItemList|ListItem|string|array $positiveNotes;
+
+	public function getPositiveNotes():ItemList|ListItem|string|array|null
+	{
+		return $this->positiveNotes??null;
+	}
+	public function setPositiveNotes(ItemList|ListItem|string|array $positiveNotes):void
+	{
+		if (is_array($positiveNotes)) {
+			$positiveNotes = $this->stringArray('positiveNotes', $positiveNotes);
+		}
+		$this->positiveNotes = $positiveNotes;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/postOfficeBoxNumberTrait.php b/inc/managers/SEO/render/Traits/_Properties/postOfficeBoxNumberTrait.php
new file mode 100644
index 0000000..06b56d1
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/postOfficeBoxNumberTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait postOfficeBoxNumberTrait {
+	/**
+	 * @var string The post office box number for PO box addresses.
+	 */
+	protected string $postOfficeBoxNumber;
+
+	public function getPostOfficeBoxNumber():?string
+	{
+		return $this->postOfficeBoxNumber??null;
+	}
+	public function setPostOfficeBoxNumber(string $postOfficeBoxNumber):void
+	{
+		$this->postOfficeBoxNumber = $postOfficeBoxNumber;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/postalAddressTrait.php b/inc/managers/SEO/render/Traits/_Properties/postalAddressTrait.php
new file mode 100644
index 0000000..dc3303d
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/postalAddressTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\ContactPoint\PostalAddress;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait postalAddressTrait {
+	/**
+	 * @var PostalAddress|string Physical address of the item.
+	 */
+	protected PostalAddress|string $postalAddress;
+
+	public function getPostalAddress():PostalAddress|string|null
+	{
+		return $this->postalAddress??null;
+	}
+	public function setPostalAddress(PostalAddress|string $postalAddress):void
+	{
+		$this->postalAddress = $postalAddress;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/postalCodeTrait.php b/inc/managers/SEO/render/Traits/_Properties/postalCodeTrait.php
new file mode 100644
index 0000000..37636ca
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/postalCodeTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait postalCodeTrait {
+	/**
+	 * @var string The postal code. For example, 94043.
+	 */
+	protected string $postalCode;
+
+	public function getPostalCode():?string
+	{
+		return $this->postalCode??null;
+	}
+	public function setPostalCode(string $postalCode):void
+	{
+		$this->postalCode = $postalCode;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/potentialActionTrait.php b/inc/managers/SEO/render/Traits/_Properties/potentialActionTrait.php
new file mode 100644
index 0000000..aa895db
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/potentialActionTrait.php
@@ -0,0 +1,149 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Action;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait potentialActionTrait {
+	use arrayHelper;
+	/**
+	 * @var Action|array Indicates a potential Action, which describes an idealized action in which this thing would play an 'object' role.
+	 */
+	protected Action|array $potentialAction;
+
+	public function getPotentialAction():Action|array|null
+	{
+		return $this->potentialAction??null;
+	}
+	public function setPotentialAction(Action|array $potentialAction):void
+	{
+		if (is_array($potentialAction)) {
+			$potentialAction = $this->classArray('potentialAction', $potentialAction, 'JVBase\managers\SEO\render\Thing\Action');
+			if (empty($potentialAction)) {
+				return;
+			}
+		}
+		$this->potentialAction = $potentialAction;
+	}
+
+	public function getPotentialActionFieldConfig():array
+	{
+		return [
+			'type'			=> 'repeater',
+			'label'			=> 'Potential Action',
+			'fields'		=> [
+				'type'	=> [
+					'type'	=> 'select',
+					'label'	=> 'Action Type',
+					'options'	=> [
+						'AchieveAction'	=> 'Achieve Action',
+						'LoseAction'	=> ' - Lose Action',
+						'TieAction'	=> ' - Tie Action',
+						'WinAction'	=> ' - Win Action',
+						'AssessAction'	=> 'Assess Action',
+						'ChooseAction'	=> ' - Choose Action',
+						'IgnoreAction'	=> ' - Ignore Action',
+						'ReactAction'	=> ' - React Action',
+						'ReviewAction'	=> ' - Review Action',
+						'ConsumeAction'	=> 'Consume Action',
+						'DrinkAction'	=> ' - Drink Action',
+						'EatAction'	=> ' - Eat Action',
+						'InstallAction'	=> ' - Install Action',
+						'ListenAction'	=> ' - Listen Action',
+						'PlayGameAction'	=> ' - PlayGame Action',
+						'ReadAction'	=> ' - Read Action',
+						'UseAction'	=> ' - Use Action',
+						'ViewAction'	=> ' - View Action',
+						'WatchAction'	=> ' - Watch Action',
+						'ControlAction'	=> 'Control Action',
+						'ActivateAction'	=> ' - Activate Action',
+						'AuthenticateAction'	=> ' - Authenticate Action',
+						'DeactivateAction'	=> ' - Deactivate Action',
+						'LoginAction'	=> ' - Login Action',
+						'ResetPasswordAction'	=> ' - ResetPassword Action',
+						'ResumeAction'	=> ' - Resume Action',
+						'SuspendAction'	=> ' - Suspend Action',
+						'CreateAction'	=> 'Create Action',
+						'CookAction'	=> ' - Cook Action',
+						'DrawAction'	=> ' - Draw Action',
+						'FilmAction'	=> ' - Film Action',
+						'PaintAction'	=> ' - Paint Action',
+						'PhotographAction'	=> ' - Photograph Action',
+						'WriteAction'	=> ' - Write Action',
+						'FindAction'	=> 'Find Action',
+						'CheckAction'	=> ' - Check Action',
+						'DiscoverAction'	=> ' - Discover Action',
+						'TrackAction'	=> ' - Track Action',
+						'InteractAction'=> 'Interact Action',
+						'BefriendAction'=> ' - Befriend Action',
+						'CommunicateAction'=> ' - Communicate Action',
+						'Follow Action'=> ' - Follow Action',
+						'JoinAction'=> ' - Join Action',
+						'LeaveAction'=> ' - Leave Action',
+						'MarryAction'=> ' - Marry Action',
+						'RegisterAction'=> ' - Register Action',
+						'SubscribeAction'=> ' - Subscribe Action',
+						'UnRegisterAction'=> ' - Unregister Action',
+						'MoveAction'	=> 'Move Action',
+						'ArriveAction'	=> ' - Arrive Action',
+						'DepartAction'	=> ' - Depart Action',
+						'TravelAction'	=> ' - Travel Action',
+						'OrganizeAction'=> 'Organize Action',
+						'AllocateAction'=> ' - Allocate Action',
+						'ApplyAction'=> ' - Apply Action',
+						'BookmarkAction'=> ' - Bookmark Action',
+						'PlanAction'=> ' - Plan Action',
+						'PlayAction'	=> 'Play Action',
+						'ExerciseAction'	=> ' - Exercise Action',
+						'PerformAction'	=> ' - Perform Action',
+						'SearchAction'	=> 'Search Action',
+						'SeekToAction'	=> 'Seek To Action',
+						'TradeAction'	=> 'Trade Action',
+						'BuyAction'	=> ' - Buy Action',
+						'OrderAction'	=> ' - Order Action',
+						'PayAction'	=> ' - Pay Action',
+						'PreOrderAction'	=> ' - PreOrder Action',
+						'QuoteAction'	=> ' - Quote Action',
+						'RentAction'	=> ' - Rent Action',
+						'SellAction'	=> ' - Sell Action',
+						'TipAction'	=> ' - Tip Action',
+						'TransferAction'=> 'Transfer Action',
+						'BorrowAction'=> ' - Borrow Action',
+						'DonateAction'=> ' - Donate Action',
+						'DownloadAction'=> ' - Download Action',
+						'GiveAction'=> ' - Give Action',
+						'LendAction'=> ' - Lend Action',
+						'MoneyAction'=> ' - Money Transfer',
+						'ReceiveAction'=> ' - Receive Action',
+						'ReturnAction'=> ' - Return Action',
+						'SendAction'=> ' - Send Action',
+						'TakeAction'=> ' - Take Action',
+						'UpdateAction'	=> 'Update Action',
+						'AddAction'	=> ' - Add Action',
+						'DeleteAction'	=> ' - Delete Action',
+						'ReplaceAction'	=> ' - Replace Action',
+					]
+				],
+				'name'	=> [
+					'type'	=> 'text',
+					'label'	=> 'Name',
+					'required'	=> true,
+					'hint' => 'Example: "Search", "Contact Us"'
+				],
+				'target'	=> [
+					'type'	=> 'text',
+					'label'	=> 'Target',
+					'required'	=> true,
+					'hint'	=> 'The URL where this action takes place.'
+				],
+				'description'	=> [
+					'type'	=> 'textarea',
+					'label'	=> 'Description',
+				]
+			]
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/predecessorOfTrait.php b/inc/managers/SEO/render/Traits/_Properties/predecessorOfTrait.php
new file mode 100644
index 0000000..c116d25
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/predecessorOfTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Product\ProductModel;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait predecessorOfTrait {
+	/**
+	 * @var ProductModel A pointer from a previous, often discontinued variant of the product to its newer variant.
+	 */
+	protected ProductModel $predecessorOf;
+
+	public function getPredecessorOf():?ProductModel
+	{
+		return $this->predecessorOf??null;
+	}
+	public function setPredecessorOf(ProductModel $predecessorOf):void
+	{
+		$this->predecessorOf = $predecessorOf;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/prepTimeTrait.php b/inc/managers/SEO/render/Traits/_Properties/prepTimeTrait.php
new file mode 100644
index 0000000..0d6bbbb
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/prepTimeTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Quantity\Duration;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait prepTimeTrait {
+	/**
+	 * @var Duration The length of time it takes to prepare the items to be used in instructions or a direction, in ISO 8601 duration format.
+	 */
+	protected Duration $prepTime;
+
+	public function getPrepTime():?Duration
+	{
+		return $this->prepTime??null;
+	}
+	public function setPrepTime(Duration $prepTime):void
+	{
+		$this->prepTime = $prepTime;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/previousItemTrait.php b/inc/managers/SEO/render/Traits/_Properties/previousItemTrait.php
new file mode 100644
index 0000000..6238d51
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/previousItemTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\ListItem;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait previousItemTrait {
+	/**
+	 * @var ListItem A link to the ListItem that precedes the current one.
+	 */
+	protected ListItem $previousItem;
+
+	public function getPreviousItem():?ListItem
+	{
+		return $this->previousItem??null;
+	}
+	public function setPreviousItem(ListItem $previousItem):void
+	{
+		$this->previousItem = $previousItem;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/previousStartDateTrait.php b/inc/managers/SEO/render/Traits/_Properties/previousStartDateTrait.php
new file mode 100644
index 0000000..fc03e23
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/previousStartDateTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\DataType\Date;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait previousStartDateTrait {
+	/**
+	 * @var Date Used in conjunction with eventStatus for rescheduled or cancelled events. This property contains the previously scheduled start date. For rescheduled events, the startDate property should be used for the newly scheduled start date. In the (rare) case of an event that has been postponed and rescheduled multiple times, this field may be repeated.
+	 */
+	protected Date $previousStartDate;
+
+	public function getPreviousStartDate():?Date
+	{
+		return $this->previousStartDate??null;
+	}
+	public function setPreviousStartDate(Date $previousStartDate):void
+	{
+		$this->previousStartDate = $previousStartDate;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/priceCurrencyTrait.php b/inc/managers/SEO/render/Traits/_Properties/priceCurrencyTrait.php
new file mode 100644
index 0000000..f1cfd66
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/priceCurrencyTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait priceCurrencyTrait {
+	/**
+	 * @var string The currency of the price, or a price component when attached to PriceSpecification and its subtypes.
+	 *
+	 * Use standard formats: ISO 4217 currency format, e.g. "USD"; Ticker symbol for cryptocurrencies, e.g. "BTC"; well known names for Local Exchange Trading Systems (LETS) and other currency types, e.g. "Ithaca HOUR".
+	 */
+	protected string $priceCurrency;
+
+	public function getPriceCurrency():?string
+	{
+		return $this->priceCurrency??null;
+	}
+	public function setPriceCurrency(string $priceCurrency):void
+	{
+		$this->priceCurrency = $priceCurrency;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/priceRangeTrait.php b/inc/managers/SEO/render/Traits/_Properties/priceRangeTrait.php
new file mode 100644
index 0000000..896b67c
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/priceRangeTrait.php
@@ -0,0 +1,30 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait priceRangeTrait {
+	/**
+	 * @var string The price range of the business, for example $$$.
+	 */
+	protected string $priceRange;
+
+	public function getPriceRange():?string
+	{
+		return $this->priceRange??null;
+	}
+	public function setPriceRange(string $priceRange):void
+	{
+		$this->priceRange = $priceRange;
+	}
+
+	public function getPriceRangeFieldConfig():array
+	{
+		return [
+			'type'	=> 'text',
+			'label'	=> 'Price Range',
+			'hint'	=> '$ - $$$$, in comparison to similar products or services in the industry'
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/priceSpecificationTrait.php b/inc/managers/SEO/render/Traits/_Properties/priceSpecificationTrait.php
new file mode 100644
index 0000000..d69bf4f
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/priceSpecificationTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\PriceSpecification;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait priceSpecificationTrait {
+	use arrayHelper;
+	/**
+	 * @var PriceSpecification|array One or more detailed price specifications, indicating the unit price and delivery or payment charges.
+	 */
+	protected PriceSpecification|array $priceSpecification;
+
+	public function getPriceSpecification():PriceSpecification|array|null
+	{
+		return $this->priceSpecification??null;
+	}
+	public function setPriceSpecification(PriceSpecification|array $priceSpecification):void
+	{
+		if (is_array($priceSpecification)) {
+			$priceSpecification = $this->classArray('priceSpecification', $priceSpecification, 'JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\PriceSpecification');
+		}
+		$this->priceSpecification = $priceSpecification;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/priceTrait.php b/inc/managers/SEO/render/Traits/_Properties/priceTrait.php
new file mode 100644
index 0000000..889d121
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/priceTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait priceTrait {
+	/**
+	 * @var int|float|string The offer price of a product, or of a price component when attached to PriceSpecification and its subtypes.
+	 *
+	 * Usage guidelines:
+	 *
+	 * Use the priceCurrency property (with standard formats: ISO 4217 currency format, e.g. "USD"; Ticker symbol for cryptocurrencies, e.g. "BTC"; well known names for Local Exchange Trading Systems (LETS) and other currency types, e.g. "Ithaca HOUR") instead of including ambiguous symbols such as '$' in the value.
+	 * Use '.' (Unicode 'FULL STOP' (U+002E)) rather than ',' to indicate a decimal point. Avoid using these symbols as a readability separator.
+	 * Note that both RDFa and Microdata syntax allow the use of a "content=" attribute for publishing simple machine-readable values alongside more human-friendly formatting.
+	 * Use values from 0123456789 (Unicode 'DIGIT ZERO' (U+0030) to 'DIGIT NINE' (U+0039)) rather than superficially similar Unicode symbols.
+	 */
+	protected int|float|string $price;
+
+	public function getPrice():int|float|string|null
+	{
+		return $this->price??null;
+	}
+	public function setPrice(int|float|string $price):void
+	{
+		$this->price = $price;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/priceValidUntilTrait.php b/inc/managers/SEO/render/Traits/_Properties/priceValidUntilTrait.php
new file mode 100644
index 0000000..3424a98
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/priceValidUntilTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\DataType\Date;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait priceValidUntilTrait {
+	/**
+	 * @var Date The date after which the price is no longer available.
+	 */
+	protected Date $priceValidUntil;
+
+	public function getPriceValidUntil():?Date
+	{
+		return $this->priceValidUntil??null;
+	}
+	public function setPriceValidUntil(Date $priceValidUntil):void
+	{
+		$this->priceValidUntil = $priceValidUntil;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/primaryImageOfPageTrait.php b/inc/managers/SEO/render/Traits/_Properties/primaryImageOfPageTrait.php
new file mode 100644
index 0000000..1848a22
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/primaryImageOfPageTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\MediaObject\ImageObject;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait primaryImageOfPageTrait {
+	/**
+	 * @var ImageObject Indicates the main image on the page.
+	 */
+	protected ImageObject $primaryImageOfPage;
+
+	public function getPrimaryImageOfPage():?ImageObject
+	{
+		return $this->primaryImageOfPage??null;
+	}
+	public function setPrimaryImageOfPage(ImageObject $primaryImageOfPage):void
+	{
+		$this->primaryImageOfPage = $primaryImageOfPage;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/processingTimeTrait.php b/inc/managers/SEO/render/Traits/_Properties/processingTimeTrait.php
new file mode 100644
index 0000000..25cc700
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/processingTimeTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Quantity\Duration;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait processingTimeTrait {
+	/**
+	 * @var Duration Estimated processing time for the service using this channel.
+	 */
+	protected Duration $processingTime;
+
+	public function getProcessingTime():?Duration
+	{
+		return $this->processingTime??null;
+	}
+	public function setProcessingTime(Duration $processingTime):void
+	{
+		$this->processingTime = $processingTime;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/producerTrait.php b/inc/managers/SEO/render/Traits/_Properties/producerTrait.php
new file mode 100644
index 0000000..da1af0e
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/producerTrait.php
@@ -0,0 +1,32 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait producerTrait {
+	use arrayHelper;
+	/**
+	 * @var Organization|Person|array The person or organization who produced the work (e.g. music album, movie, TV/radio series etc.).
+	 */
+	protected Organization|Person|array $producer;
+
+	public function getProducer():Organization|Person|array|null
+	{
+		return $this->producer??null;
+	}
+	public function setProducer(Organization|Person|array $producer):void
+	{
+		if (is_array($producer)) {
+			$producer = $this->mixedArray('producer', $producer, [
+				'JVBase\managers\SEO\render\Thing\Organization\Organization',
+				'JVBase\managers\SEO\render\Thing\Person\Person'
+			]);
+		}
+		$this->producer = $producer;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/productGroupIDTrait.php b/inc/managers/SEO/render/Traits/_Properties/productGroupIDTrait.php
new file mode 100644
index 0000000..6598814
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/productGroupIDTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait productGroupIDTrait {
+	/**
+	 * @var string Indicates a textual identifier for a ProductGroup.
+	 */
+	protected string $productGroupID;
+
+	public function getProductGroupID():?string
+	{
+		return $this->productGroupID??null;
+	}
+	public function setProductGroupID(string $productGroupID):void
+	{
+		$this->productGroupID = $productGroupID;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/productIDTrait.php b/inc/managers/SEO/render/Traits/_Properties/productIDTrait.php
new file mode 100644
index 0000000..36b4536
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/productIDTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait productIDTrait {
+	/**
+	 * @var string The product identifier, such as ISBN. For example: meta itemprop="productID" content="isbn:123-456-789".
+	 */
+	protected string $productID;
+
+	public function getProductID():?string
+	{
+		return $this->productID??null;
+	}
+	public function setProductID(string $productID):void
+	{
+		$this->productID = $productID;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/productSupportedTrait.php b/inc/managers/SEO/render/Traits/_Properties/productSupportedTrait.php
new file mode 100644
index 0000000..3641df2
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/productSupportedTrait.php
@@ -0,0 +1,31 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Product\Product;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait productSupportedTrait {
+	use arrayHelper;
+	/**
+	 * @var Product|string|array The product or service this support contact point is related to (such as product support for a particular product line). This can be a specific product or product line (e.g. "iPhone") or a general category of products or services (e.g. "smartphones").
+	 */
+	protected Product|string|array $productSupported;
+
+	public function getProductSupported():Product|string|array|null
+	{
+		return $this->productSupported??null;
+	}
+	public function setProductSupported(Product|string|array $productSupported):void
+	{
+		if (is_array($productSupported)) {
+			$productSupported = $this->mixedArray('productSupported', $productSupported, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\Product\Product'
+			]);
+		}
+		$this->productSupported = $productSupported;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/pronounsTrait.php b/inc/managers/SEO/render/Traits/_Properties/pronounsTrait.php
new file mode 100644
index 0000000..785a764
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/pronounsTrait.php
@@ -0,0 +1,26 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\DefinedTerm;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\StructuredValue;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait pronounsTrait {
+	/**
+	 * @var DefinedTerm|StructuredValue|string A short string listing or describing pronouns for a person. Typically the person concerned is the best authority as pronouns are a critical part of personal identity and expression. Publishers and consumers of this information are reminded to treat this data responsibly, take country-specific laws related to gender expression into account, and be wary of out-of-date data and drawing unwarranted inferences about the person being described.
+	 *
+	 * In English, formulations such as "they/them", "she/her", and "he/him" are commonly used online and can also be used here. We do not intend to enumerate all possible micro-syntaxes in all languages. More structured and well-defined external values for pronouns can be referenced using the StructuredValue or DefinedTerm values.
+	 */
+	protected DefinedTerm|StructuredValue|string $pronouns;
+
+	public function getPronouns():DefinedTerm|StructuredValue|string|null
+	{
+		return $this->pronouns??null;
+	}
+	public function setPronouns(DefinedTerm|StructuredValue|string $pronouns):void
+	{
+		$this->pronouns = $pronouns;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/propertyIDTrait.php b/inc/managers/SEO/render/Traits/_Properties/propertyIDTrait.php
new file mode 100644
index 0000000..8930e9c
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/propertyIDTrait.php
@@ -0,0 +1,22 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait propertyIDTrait {
+	/**
+	 * @var string A commonly used identifier for the characteristic represented by the property, e.g. a manufacturer or a standard code for a property.
+	 * For our use case, the @id of a defined item elsewhere on this site
+	 */
+	protected string $propertyID;
+
+	public function getPropertyID():?string
+	{
+		return $this->propertyID??null;
+	}
+	public function setPropertyID(string $propertyID):void
+	{
+		$this->propertyID = $propertyID;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/proteinContentTrait.php b/inc/managers/SEO/render/Traits/_Properties/proteinContentTrait.php
new file mode 100644
index 0000000..ea6b1ff
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/proteinContentTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait proteinContentTrait {
+	/**
+	 * @var int|float The number of proteinContent.
+	 */
+	protected int|float $proteinContent;
+
+	public function getProteinContent():int|float|null
+	{
+		return $this->proteinContent??null;
+	}
+	public function setProteinContent(int|float $proteinContent):void
+	{
+		$this->proteinContent = $proteinContent;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/providerMobilityTrait.php b/inc/managers/SEO/render/Traits/_Properties/providerMobilityTrait.php
new file mode 100644
index 0000000..71c62c0
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/providerMobilityTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait providerMobilityTrait {
+	/**
+	 * @var string Indicates the mobility of a provided service (e.g. 'static', 'dynamic').
+	 */
+	protected string $providerMobility;
+
+	public function getProviderMobility():?string
+	{
+		return $this->providerMobility??null;
+	}
+	public function setProviderMobility(string $providerMobility):void
+	{
+		$this->providerMobility = $providerMobility;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/providerTrait.php b/inc/managers/SEO/render/Traits/_Properties/providerTrait.php
new file mode 100644
index 0000000..f94bdcd
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/providerTrait.php
@@ -0,0 +1,32 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait providerTrait {
+	use arrayHelper;
+	/**
+	 * @var Organization|Person|string|array The service provider, service operator, or service performer; the goods producer. Another party (a seller) may offer those services or goods on behalf of the provider. A provider may also serve as the seller. Supersedes carrier.
+	 */
+	protected Organization|Person|string|array $provider;
+
+	public function getProvider():Organization|Person|array|string|null
+	{
+		return $this->provider??null;
+	}
+	public function setProvider(Organization|Person|array|string $provider):void
+	{
+		if (is_array($provider)) {
+			$provider = $this->mixedArray('provider', $provider, ['JVBase\managers\SEO\render\Thing\Organization\Organization','JVBase\managers\SEO\render\Thing\Person\Person','string']);
+			if (empty($provider)){
+				return;
+			}
+		}
+		$this->provider = $provider;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/providesServiceTrait.php b/inc/managers/SEO/render/Traits/_Properties/providesServiceTrait.php
new file mode 100644
index 0000000..4091737
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/providesServiceTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Service;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait providesServiceTrait {
+	use arrayHelper;
+	/**
+	 * @var Service|array The service provided by this channel.
+	 */
+	protected Service|array $providesService;
+
+	public function getProvidesService():Service|array|null
+	{
+		return $this->providesService??null;
+	}
+	public function setProvidesService(Service|array $providesService):void
+	{
+		if (is_array($providesService)){
+			$providesService = $this->classArray('providesService', $providesService, 'JVBase\managers\SEO\render\Thing\Intangible\Service');
+		}
+		$this->providesService = $providesService;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/publicAccessTrait.php b/inc/managers/SEO/render/Traits/_Properties/publicAccessTrait.php
new file mode 100644
index 0000000..08b6c0d
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/publicAccessTrait.php
@@ -0,0 +1,30 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait publicAccessTrait {
+	/**
+	 * @var bool A flag to signal that the item, event, or place is accessible for free. Supersedes free.
+	 */
+	protected bool $publicAccess;
+
+	public function getPublicAccess():?bool
+	{
+		return $this->publicAccess??null;
+	}
+	public function setPublicAccess(bool $publicAccess):void
+	{
+		$this->publicAccess = $publicAccess;
+	}
+
+	public function getPublicAccessFieldConfig():array
+	{
+		return [
+			'type'	=> 'true_false',
+			'label'	=> 'Publicly Accessible',
+			'hint'	=> 'A flag to signal that the Place is open to public visitors.'
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/publisherImprintTrait.php b/inc/managers/SEO/render/Traits/_Properties/publisherImprintTrait.php
new file mode 100644
index 0000000..2cf3115
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/publisherImprintTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait publisherImprintTrait {
+	/**
+	 * @var Organization The publishing division which published the comic.
+	 */
+	protected Organization $publisherImprint;
+
+	public function getPublisherImprint():?Organization
+	{
+		return $this->publisherImprint??null;
+	}
+	public function setPublisherImprint(Organization $publisherImprint):void
+	{
+		$this->publisherImprint = $publisherImprint;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/publisherTrait.php b/inc/managers/SEO/render/Traits/_Properties/publisherTrait.php
new file mode 100644
index 0000000..eff799d
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/publisherTrait.php
@@ -0,0 +1,32 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait publisherTrait {
+	use arrayHelper;
+	/**
+	 * @var Organization|Person|array The publisher of the article in question.
+	 */
+	protected Organization|Person|array $publisher;
+
+	public function getPublisher():Organization|Person|array|null
+	{
+		return $this->publisher??null;
+	}
+	public function setPublisher(Organization|Person|array $publisher):void
+	{
+		if (is_array($publisher)) {
+			$publisher = $this->mixedArray('publisher', $publisher, [
+				'JVBase\managers\SEO\render\Thing\Organization\Organization',
+				'JVBase\managers\SEO\render\Thing\Person\Person'
+			]);
+		}
+		$this->publisher = $publisher;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/publishingPrinciplesTrait.php b/inc/managers/SEO/render/Traits/_Properties/publishingPrinciplesTrait.php
new file mode 100644
index 0000000..eeaae68
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/publishingPrinciplesTrait.php
@@ -0,0 +1,29 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait publishingPrinciplesTrait {
+	/**
+	 * @var CreativeWork|string The publishingPrinciples property indicates (typically via URL) a document describing the editorial principles of an Organization (or individual, e.g. a Person writing a blog) that relate to their activities as a publisher, e.g. ethics or diversity policies. When applied to a CreativeWork (e.g. NewsArticle) the principles are those of the party primarily responsible for the creation of the CreativeWork.
+	 *
+	 * While such policies are most typically expressed in natural language, sometimes related information (e.g. indicating a funder) can be expressed using schema.org terminology.
+	 */
+	protected CreativeWork|string $publishingPrinciples;
+
+	public function getPublishingPrinciples():CreativeWork|string|null
+	{
+		return $this->publishingPrinciples??null;
+	}
+	public function setPublishingPrinciples(CreativeWork|string $publishingPrinciples):void
+	{
+		if (is_string($publishingPrinciples) && !filter_var($publishingPrinciples, FILTER_VALIDATE_URL)) {
+			error_log('[SEO] Publishing Principles should be a valid URL: '.$publishingPrinciples);
+			return;
+		}
+		$this->publishingPrinciples = $publishingPrinciples;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/qualificationsTrait.php b/inc/managers/SEO/render/Traits/_Properties/qualificationsTrait.php
new file mode 100644
index 0000000..0d04ac2
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/qualificationsTrait.php
@@ -0,0 +1,31 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\EducationalOccupationalCredential;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait qualificationsTrait {
+	use arrayHelper;
+	/**
+	 * @var EducationalOccupationalCredential|string|array Specific qualifications required for this role or Occupation.
+	 */
+	protected EducationalOccupationalCredential|string|array $qualifications;
+
+	public function getQualifications():EducationalOccupationalCredential|string|array|null
+	{
+		return $this->qualifications??null;
+	}
+	public function setQualifications(EducationalOccupationalCredential|string|array $qualifications):void
+	{
+		if (is_array($qualifications)) {
+			$qualifications = $this->mixedArray('qualifications', $qualifications, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\CreativeWork\EducationalOccupationalCredential',
+			]);
+		}
+		$this->qualifications = $qualifications;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/ratingCountTrait.php b/inc/managers/SEO/render/Traits/_Properties/ratingCountTrait.php
new file mode 100644
index 0000000..06ad8ad
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/ratingCountTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait ratingCountTrait {
+	/**
+	 * @var int The count of total number of ratings.
+	 */
+	protected int $ratingCount;
+
+	public function getRatingCount():?int
+	{
+		return $this->ratingCount??null;
+	}
+	public function setRatingCount(int $ratingCount):void
+	{
+		$this->ratingCount = $ratingCount;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/ratingValueTrait.php b/inc/managers/SEO/render/Traits/_Properties/ratingValueTrait.php
new file mode 100644
index 0000000..b7f9836
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/ratingValueTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait ratingValueTrait {
+	/**
+	 * @var int The rating for the content
+	 */
+	protected int $ratingValue;
+
+	public function getRatingValue():?int
+	{
+		return $this->ratingValue??null;
+	}
+	public function setRatingValue(int $ratingValue):void
+	{
+		$this->ratingValue = $ratingValue;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/recordedAtTrait.php b/inc/managers/SEO/render/Traits/_Properties/recordedAtTrait.php
new file mode 100644
index 0000000..4af8bde
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/recordedAtTrait.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Event\Event;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait recordedAtTrait {
+	/**
+	 * @var Event The Event where the CreativeWork was recorded. The CreativeWork may capture all or part of the event.
+	 * Inverse property: recordedIn
+	 */
+	protected Event $recordedAt;
+
+	public function getRecordedAt():?Event
+	{
+		return $this->recordedAt??null;
+	}
+	public function setRecordedAt(Event $recordedAt):void
+	{
+		$this->recordedAt = $recordedAt;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/recordedInTrait.php b/inc/managers/SEO/render/Traits/_Properties/recordedInTrait.php
new file mode 100644
index 0000000..931207f
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/recordedInTrait.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait recordedInTrait {
+	/**
+	 * @var CreativeWork The CreativeWork that captured all or part of this Event.
+	 * Inverse property: recordedAt
+	 */
+	protected CreativeWork $recordedIn;
+
+	public function getRecordedIn():?CreativeWork
+	{
+		return $this->recordedIn??null;
+	}
+	public function setRecordedIn(CreativeWork $recordedIn):void
+	{
+		$this->recordedIn = $recordedIn;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/refundTypeTrait.php b/inc/managers/SEO/render/Traits/_Properties/refundTypeTrait.php
new file mode 100644
index 0000000..2f6fce0
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/refundTypeTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\RefundTypeEnumeration;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait refundTypeTrait {
+	/**
+	 * @var RefundTypeEnumeration A refund type, from an enumerated list.
+	 */
+	protected RefundTypeEnumeration $refundType;
+
+	public function getRefundType():?RefundTypeEnumeration
+	{
+		return $this->refundType??null;
+	}
+	public function setRefundType(RefundTypeEnumeration $refundType):void
+	{
+		$this->refundType = $refundType;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/regionsAllowedTrait.php b/inc/managers/SEO/render/Traits/_Properties/regionsAllowedTrait.php
new file mode 100644
index 0000000..739a839
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/regionsAllowedTrait.php
@@ -0,0 +1,33 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\GeoShape;
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait regionsAllowedTrait {
+	use arrayHelper;
+	/**
+	 * @var GeoShape|Place|string|array The ISO 3166-1 (ISO 3166-1 alpha-2) or ISO 3166-2 code, the place, or the GeoShape for the geo-political region(s) for which the offer or delivery charge specification is not valid, e.g. a region where the transaction is not allowed.
+	 */
+	protected GeoShape|Place|string|array $regionsAllowed;
+
+	public function getRegionsAllowed():GeoShape|Place|string|array|null
+	{
+		return $this->regionsAllowed??null;
+	}
+	public function setRegionsAllowed(GeoShape|Place|string|array $regionsAllowed):void
+	{
+		if (is_array($regionsAllowed)) {
+			$regionsAllowed = $this->mixedArray('regionsAllowed', $regionsAllowed, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\GeoShape',
+				'JVBase\managers\SEO\render\Thing\Place\Place'
+			]);
+		}
+		$this->regionsAllowed = $regionsAllowed;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/relatedLinkTrait.php b/inc/managers/SEO/render/Traits/_Properties/relatedLinkTrait.php
new file mode 100644
index 0000000..e19a8c8
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/relatedLinkTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait relatedLinkTrait {
+	/**
+	 * @var string A relatedLink of the item
+	 */
+	protected string $relatedLink;
+
+	public function getRelatedLink():?string
+	{
+		return $this->relatedLink??null;
+	}
+	public function setRelatedLink(string $relatedLink):void
+	{
+		$this->relatedLink = $relatedLink;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/relatedToTrait.php b/inc/managers/SEO/render/Traits/_Properties/relatedToTrait.php
new file mode 100644
index 0000000..c5d93a8
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/relatedToTrait.php
@@ -0,0 +1,31 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait relatedToTrait {
+	use arrayHelper;
+	/**
+	 * @var Person|string|array The most generic familial relation.
+	 */
+	protected Person|string|array $relatedTo;
+
+	public function getRelatedTo():Person|string|array|null
+	{
+		return $this->relatedTo??null;
+	}
+	public function setRelatedTo(Person|string|array $relatedTo):void
+	{
+		if (is_array($relatedTo)) {
+			$relatedTo = $this->mixedArray('relatedTo', $relatedTo, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\Person\Person'
+			]);
+		}
+		$this->relatedTo = $relatedTo;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/releaseDateTrait.php b/inc/managers/SEO/render/Traits/_Properties/releaseDateTrait.php
new file mode 100644
index 0000000..1b2a41b
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/releaseDateTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\DataType\Date;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait releaseDateTrait {
+	/**
+	 * @var Date The release date of a product or product model. This can be used to distinguish the exact variant of a product.
+	 */
+	protected Date $releaseDate;
+
+	public function getReleaseDate():?Date
+	{
+		return $this->releaseDate??null;
+	}
+	public function setReleaseDate(Date $releaseDate):void
+	{
+		$this->releaseDate = $releaseDate;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/remainingAttendeeCapacityTrait.php b/inc/managers/SEO/render/Traits/_Properties/remainingAttendeeCapacityTrait.php
new file mode 100644
index 0000000..c3df329
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/remainingAttendeeCapacityTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait remainingAttendeeCapacityTrait {
+	/**
+	 * @var int The number of attendee places for an event that remain unallocated.
+	 */
+	protected int $remainingAttendeeCapacity;
+
+	public function getRemainingAttendeeCapacity():?int
+	{
+		return $this->remainingAttendeeCapacity??null;
+	}
+	public function setRemainingAttendeeCapacity(int $remainingAttendeeCapacity):void
+	{
+		$this->remainingAttendeeCapacity = $remainingAttendeeCapacity;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/repeatCountTrait.php b/inc/managers/SEO/render/Traits/_Properties/repeatCountTrait.php
new file mode 100644
index 0000000..59ab4b4
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/repeatCountTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait repeatCountTrait {
+	/**
+	 * @var int Defines the number of times a recurring Event will take place.
+	 */
+	protected int $repeatCount;
+
+	public function getRepeatCount():?int
+	{
+		return $this->repeatCount??null;
+	}
+	public function setRepeatCount(int $repeatCount):void
+	{
+		$this->repeatCount = $repeatCount;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/repeatFrequencyTrait.php b/inc/managers/SEO/render/Traits/_Properties/repeatFrequencyTrait.php
new file mode 100644
index 0000000..65d51ad
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/repeatFrequencyTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Quantity\Duration;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait repeatFrequencyTrait {
+	/**
+	 * @var Duration|string Defines the frequency at which Events will occur according to a schedule Schedule. The intervals between events should be defined as a Duration of time.
+	 */
+	protected Duration|string $repeatFrequency;
+
+	public function getRepeatFrequency():Duration|string|null
+	{
+		return $this->repeatFrequency??null;
+	}
+	public function setRepeatFrequency(Duration|string $repeatFrequency):void
+	{
+		$this->repeatFrequency = $repeatFrequency;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/representativeOfPageTrait.php b/inc/managers/SEO/render/Traits/_Properties/representativeOfPageTrait.php
new file mode 100644
index 0000000..e67996a
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/representativeOfPageTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait representativeOfPageTrait {
+	/**
+	 * @var bool Whether the image represents the content of the page
+	 */
+	protected bool $representativeOfPage;
+
+	public function getRepresentativeOfPage():?bool
+	{
+		return $this->representativeOfPage??null;
+	}
+	public function setRepresentativeOfPage(bool $representativeOfPage):void
+	{
+		$this->representativeOfPage = $representativeOfPage;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/requiredQuantityTrait.php b/inc/managers/SEO/render/Traits/_Properties/requiredQuantityTrait.php
new file mode 100644
index 0000000..9f8c4e0
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/requiredQuantityTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait requiredQuantityTrait {
+	/**
+	 * @var QuantitativeValue|float|int|string The required quantity of the item(s).
+	 */
+	protected QuantitativeValue|float|int|string $requiredQuantity;
+
+	public function getRequiredQuantity():QuantitativeValue|float|int|string|null
+	{
+		return $this->requiredQuantity??null;
+	}
+	public function setRequiredQuantity(QuantitativeValue|float|int|string $requiredQuantity):void
+	{
+		$this->requiredQuantity = $requiredQuantity;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/reservationForTrait.php b/inc/managers/SEO/render/Traits/_Properties/reservationForTrait.php
new file mode 100644
index 0000000..a2b52b2
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/reservationForTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait reservationForTrait {
+	use arrayHelper;
+	/**
+	 * @var Thing|array The thing -- flight, event, restaurant, etc. being reserved.
+	 */
+	protected Thing|array $reservationFor;
+
+	public function getReservationFor():Thing|array|null
+	{
+		return $this->reservationFor??null;
+	}
+	public function setReservationFor(Thing|array $reservationFor):void
+	{
+		if (is_array($reservationFor)) {
+			$reservationFor = $this->classArray('reservationFor', $reservationFor, 'JVBase\managers\SEO\render\Thing\Thing');
+		}
+		$this->reservationFor = $reservationFor;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/reservationIdTrait.php b/inc/managers/SEO/render/Traits/_Properties/reservationIdTrait.php
new file mode 100644
index 0000000..6612913
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/reservationIdTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait reservationIdTrait {
+	/**
+	 * @var string A reservationId of the item
+	 */
+	protected string $reservationId;
+
+	public function getReservationId():?string
+	{
+		return $this->reservationId??null;
+	}
+	public function setReservationId(string $reservationId):void
+	{
+		$this->reservationId = $reservationId;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/responsibilitiesTrait.php b/inc/managers/SEO/render/Traits/_Properties/responsibilitiesTrait.php
new file mode 100644
index 0000000..bd12b36
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/responsibilitiesTrait.php
@@ -0,0 +1,27 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait responsibilitiesTrait {
+	use arrayHelper;
+	/**
+	 * @var string|array Responsibilities associated with this role or Occupation.
+	 */
+	protected string|array $responsibilities;
+
+	public function getResponsibilities():string|array|null
+	{
+		return $this->responsibilities??null;
+	}
+	public function setResponsibilities(string|array $responsibilities):void
+	{
+		if (is_array($responsibilities)) {
+			$responsibilities = $this->stringArray('responsibilities', $responsibilities);
+		}
+		$this->responsibilities = $responsibilities;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/resultTrait.php b/inc/managers/SEO/render/Traits/_Properties/resultTrait.php
new file mode 100644
index 0000000..120ef17
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/resultTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait resultTrait {
+	/**
+	 * @var Thing The result produced in the action. E.g. John wrote a book.
+	 */
+	protected Thing $result;
+
+	public function getResult():?Thing
+	{
+		return $this->result??null;
+	}
+	public function setResult(Thing $result):void
+	{
+		$this->result = $result;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/reviewBodyTrait.php b/inc/managers/SEO/render/Traits/_Properties/reviewBodyTrait.php
new file mode 100644
index 0000000..937820e
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/reviewBodyTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait reviewBodyTrait {
+	/**
+	 * @var string The actual body of the review.
+	 */
+	protected string $reviewBody;
+
+	public function getReviewBody():?string
+	{
+		return $this->reviewBody??null;
+	}
+	public function setReviewBody(string $reviewBody):void
+	{
+		$this->reviewBody = $reviewBody;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/reviewCountTrait.php b/inc/managers/SEO/render/Traits/_Properties/reviewCountTrait.php
new file mode 100644
index 0000000..37932cd
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/reviewCountTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait reviewCountTrait {
+	/**
+	 * @var int The count of total number of reviews.
+	 */
+	protected int $reviewCount;
+
+	public function getReviewCount():?int
+	{
+		return $this->reviewCount??null;
+	}
+	public function setReviewCount(int $reviewCount):void
+	{
+		$this->reviewCount = $reviewCount;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/reviewRatingTrait.php b/inc/managers/SEO/render/Traits/_Properties/reviewRatingTrait.php
new file mode 100644
index 0000000..f985ea2
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/reviewRatingTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Rating\Rating;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait reviewRatingTrait {
+	/**
+	 * @var Rating The rating given in this review. Note that reviews can themselves be rated. The reviewRating applies to rating given by the review. The aggregateRating property applies to the review itself, as a creative work.
+	 */
+	protected Rating $reviewRating;
+
+	public function getReviewRating():?Rating
+	{
+		return $this->reviewRating??null;
+	}
+	public function setReviewRating(Rating $reviewRating):void
+	{
+		$this->reviewRating = $reviewRating;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/reviewTrait.php b/inc/managers/SEO/render/Traits/_Properties/reviewTrait.php
new file mode 100644
index 0000000..2f5a238
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/reviewTrait.php
@@ -0,0 +1,67 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\Review;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait reviewTrait {
+	use arrayHelper;
+	/**
+	 * @var Review|array A review of the item. Supersedes reviews.
+	 */
+	protected Review|array $review;
+
+	public function getReview():Review|array|null
+	{
+		return $this->review??null;
+	}
+	public function setReview(Review|array $review):void
+	{
+		if (is_array($review)) {
+			$review = $this->classArray('review', $review, 'JVBase\managers\SEO\render\Thing\CreativeWork\Review');
+		}
+		$this->review = $review;
+	}
+
+	public function getReviewFieldConfig():array
+	{
+		return [
+			'type'		=> 'repeater',
+			'label'		=> 'Review(s)',
+			'hint'		=> 'Share some relevant reviews you\'d like to highlight',
+			'fields'	=> [
+				'author'	=> [
+					'type'	=> 'text',
+					'label'	=> 'Author',
+				],
+				'reviewRating'	=> [
+					'type'	=> 'group',
+					'label'	=> 'Review Rating',
+					'fields'=> [
+						'ratingValue'	=> [
+							'type'	=> 'number',
+							'label'	=> 'Rating',
+						],
+						'bestRating'	=> [
+							'type'	=> 'number',
+							'label'	=> 'Worst Value',
+							'default'	=> 5
+						],
+						'worstRating'	=> [
+							'type'	=> 'number',
+							'label'	=> 'Worst Value',
+							'default'	=> 1
+						]
+					]
+				],
+				'reviewBody'	=> [
+					'type'	=> 'textarea',
+					'label'	=> 'Review Text'
+				]
+			]
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/reviewedByTrait.php b/inc/managers/SEO/render/Traits/_Properties/reviewedByTrait.php
new file mode 100644
index 0000000..0f672e6
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/reviewedByTrait.php
@@ -0,0 +1,32 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait reviewedByTrait {
+	use arrayHelper;
+	/**
+	 * @var Organization|Person|array People or organizations that have reviewed the content on this web page for accuracy and/or completeness.
+	 */
+	protected Organization|Person|array $reviewedBy;
+
+	public function getReviewedBy():Organization|Person|array|null
+	{
+		return $this->reviewedBy??null;
+	}
+	public function setReviewedBy(Organization|Person|array $reviewedBy):void
+	{
+		if (is_array($reviewedBy)) {
+			$reviewedBy = $this->mixedArray('reviewedBy', $reviewedBy, [
+				'JVBase\managers\SEO\render\Thing\Organization\Organization',
+				'JVBase\managers\SEO\render\Thing\Person\Person'
+			]);
+		}
+		$this->reviewedBy = $reviewedBy;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/sameAsTrait.php b/inc/managers/SEO/render/Traits/_Properties/sameAsTrait.php
new file mode 100644
index 0000000..80fc4fe
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/sameAsTrait.php
@@ -0,0 +1,48 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait sameAsTrait {
+	use arrayHelper;
+	/**
+	 * @var array|string URL of a reference Web page that unambiguously indicates the item's identity. E.g. the URL of the item's Wikipedia page, Wikidata entry, or official website.
+	 */
+	protected array|string $sameAs;
+
+	public function getSameAs():array|string|null
+	{
+		return $this->sameAs??null;
+	}
+	public function setSameAs(string|array $sameAs):void
+	{
+		if (is_array($sameAs)) {
+			$sameAs = $this->urlArray('sameAs', $sameAs);
+			if (empty($sameAs)) {
+				return;
+			}
+		} elseif (!filter_var($sameAs, FILTER_VALIDATE_URL)) {
+			error_log('[SEO]Invalid sameAs; not a URL: '.$sameAs);
+			return;
+		}
+		$this->sameAs = $sameAs;
+	}
+
+	public function getSameAsFieldConfig():array
+	{
+		return [
+			'type'	=> 'repeater',
+			'label'	=> 'Same As',
+			'hint'	=> 'List your social media presence or other links',
+			'fields'	=> [
+				'url'	=> [
+					'type'	=> 'url',
+					'label'	=> 'URL',
+				]
+			]
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/saturatedFatContentTrait.php b/inc/managers/SEO/render/Traits/_Properties/saturatedFatContentTrait.php
new file mode 100644
index 0000000..8f90679
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/saturatedFatContentTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait saturatedFatContentTrait {
+	/**
+	 * @var int|float The number of saturatedFatContent.
+	 */
+	protected int|float $saturatedFatContent;
+
+	public function getSaturatedFatContent():int|float|null
+	{
+		return $this->saturatedFatContent??null;
+	}
+	public function setSaturatedFatContent(int|float $saturatedFatContent):void
+	{
+		$this->saturatedFatContent = $saturatedFatContent;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/scheduleTimezoneTrait.php b/inc/managers/SEO/render/Traits/_Properties/scheduleTimezoneTrait.php
new file mode 100644
index 0000000..34143a9
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/scheduleTimezoneTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait scheduleTimezoneTrait {
+	/**
+	 * @var string Indicates the timezone for which the time(s) indicated in the Schedule are given. The value provided should be among those listed in the IANA Time Zone Database.
+	 */
+	protected string $scheduleTimezone;
+
+	public function getScheduleTimezone():?string
+	{
+		return $this->scheduleTimezone??null;
+	}
+	public function setScheduleTimezone(string $scheduleTimezone):void
+	{
+		$this->scheduleTimezone = $scheduleTimezone;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/sdDatePublishedTrait.php b/inc/managers/SEO/render/Traits/_Properties/sdDatePublishedTrait.php
new file mode 100644
index 0000000..13dd9d1
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/sdDatePublishedTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\DataType\Date;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait sdDatePublishedTrait {
+	/**
+	 * @var Date Indicates the date on which the current structured data was generated / published. Typically used alongside sdPublisher.
+	 */
+	protected Date $sdDatePublished;
+
+	public function getSdDatePublished():?Date
+	{
+		return $this->sdDatePublished??null;
+	}
+	public function setSdDatePublished(Date $sdDatePublished):void
+	{
+		$this->sdDatePublished = $sdDatePublished;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/sdLicenseTrait.php b/inc/managers/SEO/render/Traits/_Properties/sdLicenseTrait.php
new file mode 100644
index 0000000..ec51eb1
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/sdLicenseTrait.php
@@ -0,0 +1,27 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait sdLicenseTrait {
+	/**
+	 * @var CreativeWork|string A license document that applies to this structured data, typically indicated by URL.
+	 */
+	protected CreativeWork|string $sdLicense;
+
+	public function getSdLicense():CreativeWork|string|null
+	{
+		return $this->sdLicense??null;
+	}
+	public function setSdLicense(CreativeWork|string $sdLicense):void
+	{
+		if (is_string($sdLicense) && !filter_var($sdLicense, FILTER_VALIDATE_URL)) {
+			error_log('[SEO]sdLicense is meant to be a URL: '.$sdLicense);
+			return;
+		}
+		$this->sdLicense = $sdLicense;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/sdPublisherTrait.php b/inc/managers/SEO/render/Traits/_Properties/sdPublisherTrait.php
new file mode 100644
index 0000000..48428fd
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/sdPublisherTrait.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Person\Person;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait sdPublisherTrait {
+	/**
+	 * @var Organization|Person Indicates the party responsible for generating and publishing the current structured data markup, typically in cases where the structured data is derived automatically from existing published content but published on a different site. For example, student projects and open data initiatives often re-publish existing content with more explicitly structured metadata. The sdPublisher property helps make such practices more explicit.
+	 */
+	protected Organization|Person $sdPublisher;
+
+	public function getSdPublisher():Organization|Person|null
+	{
+		return $this->sdPublisher??null;
+	}
+	public function setSdPublisher(Organization|Person $sdPublisher):void
+	{
+		$this->sdPublisher = $sdPublisher;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/seeksTrait.php b/inc/managers/SEO/render/Traits/_Properties/seeksTrait.php
new file mode 100644
index 0000000..c3f4b85
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/seeksTrait.php
@@ -0,0 +1,39 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Demand;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait seeksTrait {
+	use arrayHelper;
+	/**
+	 * @var Demand|array A pointer to products or services sought by the organization or person (demand).
+	 */
+	protected Demand|array $seeks;
+
+	public function getSeeks():Demand|array|null
+	{
+		return $this->seeks??null;
+	}
+	public function setSeeks(Demand|array $seeks):void
+	{
+		if(is_array($seeks)){
+			$seeks = $this->classArray('seeks', $seeks, 'JVBase\managers\SEO\render\Thing\Intangible\Demand');
+		}
+		$this->seeks = $seeks;
+	}
+
+	public function getSeeksFieldConfig():array
+	{
+		return [
+			'type'	=> 'repeater',
+			'label'	=> 'Seeks',
+			'fields'=> [
+
+			]
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/sellerTrait.php b/inc/managers/SEO/render/Traits/_Properties/sellerTrait.php
new file mode 100644
index 0000000..18b05ab
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/sellerTrait.php
@@ -0,0 +1,32 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait sellerTrait {
+	use arrayHelper;
+	/**
+	 * @var Person|Organization|array An entity which offers (sells / leases / lends / loans) the services / goods. A seller may also be a provider. Supersedes merchant, vendor.
+	 */
+	protected Person|Organization|array $seller;
+
+	public function getSeller():Person|Organization|array|null
+	{
+		return $this->seller??null;
+	}
+	public function setSeller(Person|Organization|array $seller):void
+	{
+		if (is_array($seller)) {
+			$seller = $this->mixedArray('seller', $seller, [
+				'JVBase\managers\SEO\render\Thing\Organization\Organization',
+				'JVBase\managers\SEO\render\Thing\Person\Person'
+			]);
+		}
+		$this->seller = $seller;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/serialNumberTrait.php b/inc/managers/SEO/render/Traits/_Properties/serialNumberTrait.php
new file mode 100644
index 0000000..a3f7f4a
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/serialNumberTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait serialNumberTrait {
+	/**
+	 * @var string The serial number or any alphanumeric identifier of a particular product. When attached to an offer, it is a shortcut for the serial number of the product included in the offer.
+	 */
+	protected string $serialNumber;
+
+	public function getSerialNumber():?string
+	{
+		return $this->serialNumber??null;
+	}
+	public function setSerialNumber(string $serialNumber):void
+	{
+		$this->serialNumber = $serialNumber;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/servesCuisineTrait.php b/inc/managers/SEO/render/Traits/_Properties/servesCuisineTrait.php
new file mode 100644
index 0000000..a3be0e2
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/servesCuisineTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait servesCuisineTrait {
+	/**
+	 * @var string The cuisine of the restaurant.
+	 */
+	protected string $servesCuisine;
+
+	public function getServesCuisine():?string
+	{
+		return $this->servesCuisine??null;
+	}
+	public function setServesCuisine(string $servesCuisine):void
+	{
+		$this->servesCuisine = $servesCuisine;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/serviceLocationTrait.php b/inc/managers/SEO/render/Traits/_Properties/serviceLocationTrait.php
new file mode 100644
index 0000000..3d20b93
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/serviceLocationTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait serviceLocationTrait {
+	use arrayHelper;
+	/**
+	 * @var Place|array The location (e.g. civic structure, local business, etc.) where a person can go to access the service.
+	 */
+	protected Place|array $serviceLocation;
+
+	public function getServiceLocation():Place|array|null
+	{
+		return $this->serviceLocation??null;
+	}
+	public function setServiceLocation(Place|array $serviceLocation):void
+	{
+		if (is_array($serviceLocation)) {
+			$serviceLocation = $this->classArray('serviceLocation', $serviceLocation, 'JVBase\managers\SEO\render\Thing\Place\Place');
+		}
+		$this->serviceLocation = $serviceLocation;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/serviceOutputTrait.php b/inc/managers/SEO/render/Traits/_Properties/serviceOutputTrait.php
new file mode 100644
index 0000000..9ed3d46
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/serviceOutputTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Thing;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait serviceOutputTrait {
+	use ArrayHelper;
+	/**
+	 * @var Thing|array The tangible thing generated by the service, e.g. a passport, permit, etc. Supersedes produces.
+	 */
+	protected Thing|array $serviceOutput;
+
+	public function getServiceOutput():Thing|array|null
+	{
+		return $this->serviceOutput??null;
+	}
+	public function setServiceOutput(Thing|array $serviceOutput):void
+	{
+		if (is_array($serviceOutput)) {
+			$serviceOutput = $this->classArray('serviceOutput', $serviceOutput, 'JVBase\managers\SEO\render\Thing\Thing');
+		}
+		$this->serviceOutput = $serviceOutput;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/servicePhoneTrait.php b/inc/managers/SEO/render/Traits/_Properties/servicePhoneTrait.php
new file mode 100644
index 0000000..d556323
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/servicePhoneTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\ContactPoint;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait servicePhoneTrait {
+	use arrayHelper;
+	/**
+	 * @var ContactPoint|array The phone number to use to access the service.
+	 */
+	protected ContactPoint|array $servicePhone;
+
+	public function getServicePhone():ContactPoint|array|null
+	{
+		return $this->servicePhone??null;
+	}
+	public function setServicePhone(ContactPoint|array $servicePhone):void
+	{
+		if (is_array($servicePhone)){
+			$servicePhone = $this->classArray('servicePhone', $servicePhone, 'JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\ContactPoint');
+		}
+		$this->servicePhone = $servicePhone;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/servicePostalAddressTrait.php b/inc/managers/SEO/render/Traits/_Properties/servicePostalAddressTrait.php
new file mode 100644
index 0000000..74a3718
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/servicePostalAddressTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\ContactPoint\PostalAddress;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait servicePostalAddressTrait {
+	/**
+	 * @var PostalAddress The address for accessing the service by mail.
+	 */
+	protected PostalAddress $servicePostalAddress;
+
+	public function getServicePostalAddress():?PostalAddress
+	{
+		return $this->servicePostalAddress??null;
+	}
+	public function setServicePostalAddress(PostalAddress $servicePostalAddress):void
+	{
+		$this->servicePostalAddress = $servicePostalAddress;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/serviceSmsNumberTrait.php b/inc/managers/SEO/render/Traits/_Properties/serviceSmsNumberTrait.php
new file mode 100644
index 0000000..b9e57e3
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/serviceSmsNumberTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\ContactPoint;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait serviceSmsNumberTrait {
+	use arrayHelper;
+	/**
+	 * @var ContactPoint|array The number to access the service by text message.
+	 */
+	protected ContactPoint|array $serviceSmsNumber;
+
+	public function getServiceSmsNumber():ContactPoint|array|null
+	{
+		return $this->serviceSmsNumber??null;
+	}
+	public function setServiceSmsNumber(ContactPoint|array $serviceSmsNumber):void
+	{
+		if (is_array($serviceSmsNumber)){
+			$serviceSmsNumber = $this->classArray('serviceSmsNumber', $serviceSmsNumber, 'JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\ContactPoint');
+		}
+		$this->serviceSmsNumber = $serviceSmsNumber;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/serviceTypeTrait.php b/inc/managers/SEO/render/Traits/_Properties/serviceTypeTrait.php
new file mode 100644
index 0000000..7860e06
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/serviceTypeTrait.php
@@ -0,0 +1,31 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\GovernmentBenefitsType;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait serviceTypeTrait {
+	use arrayHelper;
+	/**
+	 * @var GovernmentBenefitsType|string|array The type of service being offered, e.g. veterans' benefits, emergency relief, etc.
+	 */
+	protected GovernmentBenefitsType|string|array $serviceType;
+
+	public function getServiceType():GovernmentBenefitsType|string|array|null
+	{
+		return $this->serviceType??null;
+	}
+	public function setServiceType(GovernmentBenefitsType|string|array $serviceType):void
+	{
+		if (is_array($serviceType)) {
+			$serviceType = $this->mixedArray('serviceType', $serviceType, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\Intangible\Enumeration\GovernmentBenefitsType'
+			]);
+		}
+		$this->serviceType = $serviceType;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/serviceUrlTrait.php b/inc/managers/SEO/render/Traits/_Properties/serviceUrlTrait.php
new file mode 100644
index 0000000..d9d789b
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/serviceUrlTrait.php
@@ -0,0 +1,25 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait serviceUrlTrait {
+	/**
+	 * @var string URL of the item.
+	 */
+	protected string $serviceUrl;
+
+	public function getServiceUrl():?string
+	{
+		return $this->serviceUrl??null;
+	}
+	public function setServiceUrl(string $serviceUrl):void
+	{
+		if (!filter_var($serviceUrl, FILTER_VALIDATE_URL)) {
+			error_log('[SEO]Not a valid serviceUrl: '.$serviceUrl);
+			return;
+		}
+		$this->serviceUrl = $serviceUrl;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/servingSizeTrait.php b/inc/managers/SEO/render/Traits/_Properties/servingSizeTrait.php
new file mode 100644
index 0000000..6656338
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/servingSizeTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait servingSizeTrait {
+	/**
+	 * @var string The serving size, in terms of the number of volume or mass
+	 */
+	protected string $servingSize;
+
+	public function getServingSize():?string
+	{
+		return $this->servingSize??null;
+	}
+	public function setServingSize(string $servingSize):void
+	{
+		$this->servingSize = $servingSize;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/siblingTrait.php b/inc/managers/SEO/render/Traits/_Properties/siblingTrait.php
new file mode 100644
index 0000000..bf6de24
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/siblingTrait.php
@@ -0,0 +1,31 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait siblingTrait {
+	use arrayHelper;
+	/**
+	 * @var Person|string|array A sibling of the person. Supersedes siblings.
+	 */
+	protected Person|string|array $sibling;
+
+	public function getSibling():Person|string|array|null
+	{
+		return $this->sibling??null;
+	}
+	public function setSibling(Person|string|array $sibling):void
+	{
+		if (is_array($sibling)) {
+			$sibling = $this->mixedArray('sibling', $sibling, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\Person\Person'
+			]);
+		}
+		$this->sibling = $sibling;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/significantLinkTrait.php b/inc/managers/SEO/render/Traits/_Properties/significantLinkTrait.php
new file mode 100644
index 0000000..714945c
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/significantLinkTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait significantLinkTrait {
+	/**
+	 * @var string One of the more significant URLs on the page. Typically, these are the non-navigation links that are clicked on the most. Supersedes significantLinks.
+	 */
+	protected string $significantLink;
+
+	public function getSignificantLink():?string
+	{
+		return $this->significantLink??null;
+	}
+	public function setSignificantLink(string $significantLink):void
+	{
+		$this->significantLink = $significantLink;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/sizeTrait.php b/inc/managers/SEO/render/Traits/_Properties/sizeTrait.php
new file mode 100644
index 0000000..0cf8e29
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/sizeTrait.php
@@ -0,0 +1,25 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\DefinedTerm;
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\SizeSpecification;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait sizeTrait {
+	/**
+	 * @var DefinedTerm|QuantitativeValue|SizeSpecification|string A standardized size of a product or creative work, specified either through a simple textual string (for example 'XL', '32Wx34L'), a QuantitativeValue with a unitCode, or a comprehensive and structured SizeSpecification; in other cases, the width, height, depth and weight properties may be more applicable.
+	 */
+	protected DefinedTerm|QuantitativeValue|SizeSpecification|string $size;
+
+	public function getSize():DefinedTerm|QuantitativeValue|SizeSpecification|string|null
+	{
+		return $this->size??null;
+	}
+	public function setSize(DefinedTerm|QuantitativeValue|SizeSpecification|string $size):void
+	{
+		$this->size = $size;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/skillsTrait.php b/inc/managers/SEO/render/Traits/_Properties/skillsTrait.php
new file mode 100644
index 0000000..384cbeb
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/skillsTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\DefinedTerm;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait skillsTrait {
+	use arrayHelper;
+	/**
+	 * @var DefinedTerm|string|array A statement of knowledge, skill, ability, task or any other assertion expressing a competency that is either claimed by a person, an organization or desired or required to fulfill a role or to work in an occupation.
+	 */
+	protected DefinedTerm|string|array $skills;
+
+	public function getSkills():DefinedTerm|string|array|null
+	{
+		return $this->skills??null;
+	}
+	public function setSkills(DefinedTerm|string|array $skills):void
+	{
+		if (is_array($skills)) {
+			$skills = $this->mixedArray('skills', $skills, ['string', 'JVBase\managers\SEO\render\Thing\Intangible\DefinedTerm']);
+		}
+		$this->skills = $skills;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/skuTrait.php b/inc/managers/SEO/render/Traits/_Properties/skuTrait.php
new file mode 100644
index 0000000..f29e6b4
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/skuTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait skuTrait {
+	/**
+	 * @var string The Stock Keeping Unit (SKU), i.e. a merchant-specific identifier for a product or service, or the product to which the offer refers.
+	 */
+	protected string $sku;
+
+	public function getSku():?string
+	{
+		return $this->sku??null;
+	}
+	public function setSku(string $sku):void
+	{
+		$this->sku = $sku;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/sloganTrait.php b/inc/managers/SEO/render/Traits/_Properties/sloganTrait.php
new file mode 100644
index 0000000..dca604c
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/sloganTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait sloganTrait {
+	/**
+	 * @var string A slogan of the item
+	 */
+	protected string $slogan;
+
+	public function getSlogan():?string
+	{
+		return $this->slogan??null;
+	}
+	public function setSlogan(string $slogan):void
+	{
+		$this->slogan = $slogan;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/smokingAllowedTrait.php b/inc/managers/SEO/render/Traits/_Properties/smokingAllowedTrait.php
new file mode 100644
index 0000000..5ea1655
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/smokingAllowedTrait.php
@@ -0,0 +1,31 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait smokingAllowedTrait {
+	/**
+	 * @var bool Indicates whether it is allowed to smoke in the place, e.g. in the restaurant, hotel or hotel room.
+	 */
+	protected bool $smokingAllowed;
+
+	public function getSmokingAllowed():?bool
+	{
+		return $this->smokingAllowed??null;
+	}
+	public function setSmokingAllowed(bool $smokingAllowed):void
+	{
+		$this->smokingAllowed = $smokingAllowed;
+	}
+
+
+	public function getSmokingAllowedFieldConfig():array
+	{
+		return [
+			'type'	=> 'true_false',
+			'label'	=> 'Smoking Allowed',
+			'hint'	=> 'Indicates whether it is allowed to smoke in the place, e.g. in the restaurant, hotel or hotel room.'
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/sodiumContentTrait.php b/inc/managers/SEO/render/Traits/_Properties/sodiumContentTrait.php
new file mode 100644
index 0000000..d9237d2
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/sodiumContentTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait sodiumContentTrait {
+	/**
+	 * @var int|float The number of milligrams of sodium
+	 */
+	protected int|float $sodiumContent;
+
+	public function getSodiumContent():int|float|null
+	{
+		return $this->sodiumContent??null;
+	}
+	public function setSodiumContent(int|float $sodiumContent):void
+	{
+		$this->sodiumContent = $sodiumContent;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/sourceOrganizationTrait.php b/inc/managers/SEO/render/Traits/_Properties/sourceOrganizationTrait.php
new file mode 100644
index 0000000..28cf23f
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/sourceOrganizationTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait sourceOrganizationTrait {
+	/**
+	 * @var Organization The Organization on whose behalf the creator was working.
+	 */
+	protected Organization $sourceOrganization;
+
+	public function getSourceOrganization():?Organization
+	{
+		return $this->sourceOrganization??null;
+	}
+	public function setSourceOrganization(Organization $sourceOrganization):void
+	{
+		$this->sourceOrganization = $sourceOrganization;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/spatialCoverageTrait.php b/inc/managers/SEO/render/Traits/_Properties/spatialCoverageTrait.php
new file mode 100644
index 0000000..3f28f06
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/spatialCoverageTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait spatialCoverageTrait {
+	/**
+	 * @var Place The spatialCoverage of a CreativeWork indicates the place(s) which are the focus of the content. It is a subproperty of contentLocation intended primarily for more technical and detailed materials. For example with a Dataset, it indicates areas that the dataset describes: a dataset of New York weather would have spatialCoverage which was the place: the state of New York.
+	 */
+	protected Place $spatialCoverage;
+
+	public function getSpatialCoverage():?Place
+	{
+		return $this->spatialCoverage??null;
+	}
+	public function setSpatialCoverage(Place $spatialCoverage):void
+	{
+		$this->spatialCoverage = $spatialCoverage;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/spatialTrait.php b/inc/managers/SEO/render/Traits/_Properties/spatialTrait.php
new file mode 100644
index 0000000..67ab34f
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/spatialTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Place\Place;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait spatialTrait {
+	/**
+	 * @var Place The "spatial" property can be used in cases when more specific properties (e.g. locationCreated, spatialCoverage, contentLocation) are not known to be appropriate.
+	 */
+	protected Place $spatial;
+
+	public function getSpatial():?Place
+	{
+		return $this->spatial??null;
+	}
+	public function setSpatial(Place $spatial):void
+	{
+		$this->spatial = $spatial;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/specialOpeningHoursTrait.php b/inc/managers/SEO/render/Traits/_Properties/specialOpeningHoursTrait.php
new file mode 100644
index 0000000..2b93075
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/specialOpeningHoursTrait.php
@@ -0,0 +1,61 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\OpeningHoursSpecification;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait specialOpeningHoursTrait {
+	/**
+	 * @var OpeningHoursSpecification The special opening hours of a certain place.
+	 *
+	 * Use this to explicitly override general opening hours brought in scope by specialOpeningHours or openingHours.
+	 */
+	protected OpeningHoursSpecification $specialOpeningHours;
+
+	public function getSpecialOpeningHours():?OpeningHoursSpecification
+	{
+		return $this->specialOpeningHours??null;
+	}
+	public function setSpecialOpeningHours(OpeningHoursSpecification $specialOpeningHours):void
+	{
+		$this->specialOpeningHours = $specialOpeningHours;
+	}
+
+	public function getSpecialOpeningHoursFieldConfig():array
+	{
+		return [
+			'type'	=> 'repeater',
+			'label'	=> 'Special Opening Hours',
+			'fields'	=> [
+				'closed'	=> [
+					'type'	=> 'true_false',
+					'label'	=> 'Is closed',
+				],
+				'dayOfWeek'	=> [
+					'type'	=> 'radio',
+					'label'	=> 'Day(s) of Week',
+					'options'	=> [
+						'Mo'	=> 'Monday',
+						'Tu'	=> 'Tuesday',
+						'We'	=> 'Wednesday',
+						'Th'	=> 'Thursday',
+						'Fr'	=> 'Friday',
+						'Sa'	=> 'Saturday',
+						'Su'	=> 'Sunday'
+					]
+				],
+				'opens'	=> [
+					'type'	=> 'time',
+					'label'	=> 'Opens At',
+				],
+				'closes'	=> [
+					'type'	=> 'time',
+					'label'	=> 'Closes At',
+				],
+			],
+			'hint'	=> 'Special opening hours that override existing hours.'
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/sponsorTrait.php b/inc/managers/SEO/render/Traits/_Properties/sponsorTrait.php
new file mode 100644
index 0000000..320b408
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/sponsorTrait.php
@@ -0,0 +1,32 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait sponsorTrait {
+	use arrayHelper;
+	/**
+	 * @var Organization|Person|array A person or organization that supports a thing through a pledge, promise, or financial contribution. E.g. a sponsor of a Medical Study or a corporate sponsor of an event.
+	 */
+	protected Organization|Person|array $sponsor;
+
+	public function getSponsor():Organization|Person|array|null
+	{
+		return $this->sponsor??null;
+	}
+	public function setSponsor(Organization|Person|array $sponsor):void
+	{
+		if (is_array($sponsor)){
+			$sponsor = $this->mixedArray('sponsor', $sponsor, [
+				'JVBase\managers\SEO\render\Thing\Organization\Organization',
+				'JVBase\managers\SEO\render\Thing\Person\Person'
+			]);
+		}
+		$this->sponsor = $sponsor;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/spouseTrait.php b/inc/managers/SEO/render/Traits/_Properties/spouseTrait.php
new file mode 100644
index 0000000..c255efd
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/spouseTrait.php
@@ -0,0 +1,31 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait spouseTrait {
+	use arrayHelper;
+	/**
+	 * @var Person|string|array The person's spouse.
+	 */
+	protected Person|string|array $spouse;
+
+	public function getSpouse():Person|string|array|null
+	{
+		return $this->spouse??null;
+	}
+	public function setSpouse(Person|string|array $spouse):void
+	{
+		if (is_array($spouse)) {
+			$spouse = $this->mixedArray('spouse', $spouse, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\Person\Person'
+			]);
+		}
+		$this->spouse = $spouse;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/starRatingTrait.php b/inc/managers/SEO/render/Traits/_Properties/starRatingTrait.php
new file mode 100644
index 0000000..dfccde9
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/starRatingTrait.php
@@ -0,0 +1,29 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Rating\Rating;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait starRatingTrait {
+	use arrayHelper;
+	/**
+	 * @var Rating|array An official rating for a lodging business or food establishment, e.g. from national associations or standards bodies.
+	 * Use the author property to indicate the rating organization, e.g. as an Organization with name such as (e.g. HOTREC, DEHOGA, WHR, or Hotelstars).
+	 */
+	protected Rating|array $starRating;
+
+	public function getStarRating():Rating|array|null
+	{
+		return $this->starRating??null;
+	}
+	public function setStarRating(Rating|array $starRating):void
+	{
+		if (is_array($starRating)) {
+			$starRating = $this->classArray('starRating', $starRating, 'JVBase\managers\SEO\render\Thing\Intangible\Rating\Rating');
+		}
+		$this->starRating = $starRating;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/startDateTrait.php b/inc/managers/SEO/render/Traits/_Properties/startDateTrait.php
new file mode 100644
index 0000000..c3ac0c4
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/startDateTrait.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\DataType\Date;
+use JVBase\managers\SEO\render\DataType\DateTime;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait startDateTrait {
+	/**
+	 * @var Date|DateTime The start date and time of the item (in ISO 8601 date format).
+	 */
+	protected Date|DateTime $startDate;
+
+	public function getStartDate():Date|DateTime|null
+	{
+		return $this->startDate??null;
+	}
+	public function setStartDate(Date|DateTime $startDate):void
+	{
+		$this->startDate = $startDate;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/startOffsetTrait.php b/inc/managers/SEO/render/Traits/_Properties/startOffsetTrait.php
new file mode 100644
index 0000000..f2d1ed2
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/startOffsetTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait startOffsetTrait {
+	/**
+	 * @var int|float The start time of the clip expressed as the number of seconds from the beginning of the work.
+	 */
+	protected int|float $startOffset;
+
+	public function getStartOffset():int|float|null
+	{
+		return $this->startOffset??null;
+	}
+	public function setStartOffset(int|float $startOffset):void
+	{
+		$this->startOffset = $startOffset;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/startTimeTrait.php b/inc/managers/SEO/render/Traits/_Properties/startTimeTrait.php
new file mode 100644
index 0000000..56d65d5
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/startTimeTrait.php
@@ -0,0 +1,26 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\DataType\DateTime;
+use JVBase\managers\SEO\render\DataType\Time;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait startTimeTrait {
+	/**
+	 * @var Time|DateTime The startTime of something. For a reserved event or service (e.g. FoodEstablishmentReservation), the time that it is expected to end. For actions that span a period of time, when the action was performed. E.g. John wrote a book from January to December. For media, including audio and video, it's the time offset of the end of a clip within a larger file.
+	 *
+	 * Note that Event uses startDate/endDate instead of startTime/startTime, even when describing dates with times. This situation may be clarified in future revisions.
+	 */
+	protected Time|DateTime $startTime;
+
+	public function getStartTime():Time|DateTime|null
+	{
+		return $this->startTime??null;
+	}
+	public function setStartTime(Time|DateTime $startTime):void
+	{
+		$this->startTime = $startTime;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/streetAddressTrait.php b/inc/managers/SEO/render/Traits/_Properties/streetAddressTrait.php
new file mode 100644
index 0000000..474bca9
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/streetAddressTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait streetAddressTrait {
+	/**
+	 * @var string The street address. For example, 1600 Amphitheatre Pkwy.
+	 */
+	protected string $streetAddress;
+
+	public function getStreetAddress():?string
+	{
+		return $this->streetAddress??null;
+	}
+	public function setStreetAddress(string $streetAddress):void
+	{
+		$this->streetAddress = $streetAddress;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/subEventTrait.php b/inc/managers/SEO/render/Traits/_Properties/subEventTrait.php
new file mode 100644
index 0000000..3c0dd7b
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/subEventTrait.php
@@ -0,0 +1,29 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Event\Event;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait subEventTrait {
+	use arrayHelper;
+	/**
+	 * @var Event|array An Event that is part of this event. For example, a conference event includes many presentations, each of which is a subEvent of the conference. Supersedes subEvents.
+	 * Inverse property: superEvent
+	 */
+	protected Event|array $subEvent;
+
+	public function getSubEvent():Event|array|null
+	{
+		return $this->subEvent??null;
+	}
+	public function setSubEvent(Event|array $subEvent):void
+	{
+		if (is_array($subEvent)) {
+			$subEvent = $this->classArray('subEvent', $subEvent, 'JVBase\managers\SEO\render\Thing\Event\Event');
+		}
+		$this->subEvent = $subEvent;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/subOrganizationTrait.php b/inc/managers/SEO/render/Traits/_Properties/subOrganizationTrait.php
new file mode 100644
index 0000000..aef8c8d
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/subOrganizationTrait.php
@@ -0,0 +1,48 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait subOrganizationTrait {
+	use arrayHelper;
+	/**
+	 * @var Organization|array The larger organization that this organization is a subOrganization of, if any. Supersedes branchOf.
+	 * Inverse property: subOrganization
+	 */
+	protected Organization|array $subOrganization;
+
+	public function getSubOrganization():Organization|array|null
+	{
+		return $this->subOrganization??null;
+	}
+	public function setSubOrganization(Organization|array $subOrganization):void
+	{
+		if (is_array($this->subOrganization)) {
+			$subOrganization = $this->classArray('subOrganization', $subOrganization, 'JVBase\managers\SEO\render\Thing\Organization\Organization');
+		}
+		$this->subOrganization = $subOrganization;
+	}
+
+	public function getSubOrganizationFieldConfig():array
+	{
+		return [
+			'type'	=> 'group',
+			'wrap'	=> 'details',
+			'label'	=> 'Sub Organization',
+			'fields'	=> [
+				'name'	=> [
+					'type'	=> 'text',
+					'label'	=> 'Name of Thing.'
+				],
+				'url'	=> [
+					'type'	=> 'url',
+					'label'	=> 'URL of where to find Thing.'
+				]
+			]
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/subjectOfTrait.php b/inc/managers/SEO/render/Traits/_Properties/subjectOfTrait.php
new file mode 100644
index 0000000..d048935
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/subjectOfTrait.php
@@ -0,0 +1,33 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+use JVBase\managers\SEO\render\Thing\Event\Event;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait subjectOfTrait {
+	use arrayHelper;
+	/**
+	 * @var CreativeWork|Event|array A CreativeWork or Event about this Thing.
+	 * Inverse property: about
+	 */
+	protected CreativeWork|Event|array $subjectOf;
+
+	public function getSubjectOf():CreativeWork|Event|array|null
+	{
+		return $this->subjectOf??null;
+	}
+	public function setSubjectOf(CreativeWork|Event|array $subjectOf):void
+	{
+		if (is_array($subjectOf)) {
+			$subjectOf = $this->mixedArray('subjectOf', $subjectOf, ['JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork', 'JVBase\managers\SEO\render\Thing\Event\Event']);
+			if (empty($subjectOf)){
+				return;
+			}
+		}
+		$this->subjectOf = $subjectOf;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/successorOfTrait.php b/inc/managers/SEO/render/Traits/_Properties/successorOfTrait.php
new file mode 100644
index 0000000..43dba4d
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/successorOfTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Product\ProductModel;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait successorOfTrait {
+	/**
+	 * @var ProductModel A pointer from a previous, often discontinued variant of the product to its newer variant.
+	 */
+	protected ProductModel $successorOf;
+
+	public function getSuccessorOf():?ProductModel
+	{
+		return $this->successorOf??null;
+	}
+	public function setSuccessorOf(ProductModel $successorOf):void
+	{
+		$this->successorOf = $successorOf;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/sugarContentTrait.php b/inc/managers/SEO/render/Traits/_Properties/sugarContentTrait.php
new file mode 100644
index 0000000..27fd9ba
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/sugarContentTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait sugarContentTrait {
+	/**
+	 * @var int|float The number of grams of sugar
+	 */
+	protected int|float $sugarContent;
+
+	public function getSugarContent():int|float|null
+	{
+		return $this->sugarContent??null;
+	}
+	public function setSugarContent(int|float $sugarContent):void
+	{
+		$this->sugarContent = $sugarContent;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/suitableForDietTrait.php b/inc/managers/SEO/render/Traits/_Properties/suitableForDietTrait.php
new file mode 100644
index 0000000..26e046c
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/suitableForDietTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\RestrictedDiet;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait suitableForDietTrait {
+	/**
+	 * @var RestrictedDiet Indicates a dietary restriction or guideline for which this recipe or menu item is suitable, e.g. diabetic, halal etc.
+	 */
+	protected RestrictedDiet $suitableForDiet;
+
+	public function getSuitableForDiet():?RestrictedDiet
+	{
+		return $this->suitableForDiet??null;
+	}
+	public function setSuitableForDiet(RestrictedDiet $suitableForDiet):void
+	{
+		$this->suitableForDiet = $suitableForDiet;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/superEventTrait.php b/inc/managers/SEO/render/Traits/_Properties/superEventTrait.php
new file mode 100644
index 0000000..7fad6a6
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/superEventTrait.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Event\Event;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait superEventTrait {
+	/**
+	 * @var Event An event that this event is a part of. For example, a collection of individual music performances might each have a music festival as their superEvent.
+	 * Inverse property: subEvent
+	 */
+	protected Event $superEvent;
+
+	public function getSuperEvent():?Event
+	{
+		return $this->superEvent??null;
+	}
+	public function setSuperEvent(Event $superEvent):void
+	{
+		$this->superEvent = $superEvent;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/targetTrait.php b/inc/managers/SEO/render/Traits/_Properties/targetTrait.php
new file mode 100644
index 0000000..f9967fd
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/targetTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\EntryPoint;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait targetTrait {
+	/**
+	 * @var EntryPoint|string Indicates a target EntryPoint, or url, for an Action.
+	 */
+	protected EntryPoint|string $description;
+
+	public function getDescription():EntryPoint|string|null
+	{
+		return $this->description??null;
+	}
+	public function setDescription(EntryPoint|string $description):void
+	{
+		$this->description = $description;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/taxIDTrait.php b/inc/managers/SEO/render/Traits/_Properties/taxIDTrait.php
new file mode 100644
index 0000000..9ed7a66
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/taxIDTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait taxIDTrait {
+	/**
+	 * @var string The Tax / Fiscal ID of the organization or person, e.g. the TIN in the US or the CIF/NIF in Spain.
+	 */
+	protected string $taxID;
+
+	public function getTaxID():?string
+	{
+		return $this->taxID??null;
+	}
+	public function setTaxID(string $taxID):void
+	{
+		$this->taxID = $taxID;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/teachesTrait.php b/inc/managers/SEO/render/Traits/_Properties/teachesTrait.php
new file mode 100644
index 0000000..c0892ce
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/teachesTrait.php
@@ -0,0 +1,25 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\DefinedTerm;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait teachesTrait {
+	use arrayHelper;
+	/**
+	 * @var DefinedTerm|string|array The item being described is intended to help a person learn the competency or learning outcome defined by the referenced term.
+	 */
+	protected DefinedTerm|string|array $teaches;
+
+	public function getTeaches():DefinedTerm|string|array|null
+	{
+		return $this->teaches??null;
+	}
+	public function setTeaches(DefinedTerm|string|array $teaches):void
+	{
+		$this->teaches = $teaches;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/telephoneTrait.php b/inc/managers/SEO/render/Traits/_Properties/telephoneTrait.php
new file mode 100644
index 0000000..aa8019e
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/telephoneTrait.php
@@ -0,0 +1,35 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\meta\Sanitizer;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait telephoneTrait {
+	/**
+	 * @var string The telephone number
+	 */
+	protected string $telephone;
+
+	public function getTelephone():?string
+	{
+		return $this->telephone??null;
+	}
+	public function setTelephone(string $telephone):void
+	{
+		if (Sanitizer::sanitizePhone($telephone) === '') {
+			error_log('[SEO]Telephone is not valid');
+			return;
+		}
+		$this->telephone = $telephone;
+	}
+
+	public function getTelephoneFieldConfig():array
+	{
+		return [
+			'type'	=> 'tel',
+			'label'	=> 'Telephone',
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/temporalCoverageTrait.php b/inc/managers/SEO/render/Traits/_Properties/temporalCoverageTrait.php
new file mode 100644
index 0000000..4b763e3
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/temporalCoverageTrait.php
@@ -0,0 +1,25 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\DataType\DateTime;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait temporalCoverageTrait {
+	/**
+	 * @var DateTime|string The temporalCoverage of a CreativeWork indicates the period that the content applies to, i.e. that it describes, either as a DateTime or as a textual string indicating a time period in ISO 8601 time interval format. In the case of a Dataset it will typically indicate the relevant time period in a precise notation (e.g. for a 2011 census dataset, the year 2011 would be written "2011/2012"). Other forms of content, e.g. ScholarlyArticle, Book, TVSeries or TVEpisode, may indicate their temporalCoverage in broader terms - textually or via well-known URL. Written works such as books may sometimes have precise temporal coverage too, e.g. a work set in 1939 - 1945 can be indicated in ISO 8601 interval format format via "1939/1945".
+	 *
+	 * Open-ended date ranges can be written with ".." in place of the end date. For example, "2015-11/.." indicates a range beginning in November 2015 and with no specified final date. This is tentative and might be updated in future when ISO 8601 is officially updated. Supersedes datasetTimeInterval.
+	 */
+	protected DateTime|string $temporalCoverage;
+
+	public function getTemporalCoverage():DateTime|string|null
+	{
+		return $this->temporalCoverage??null;
+	}
+	public function setTemporalCoverage(DateTime|string $temporalCoverage):void
+	{
+		$this->temporalCoverage = $temporalCoverage;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/termCodeTrait.php b/inc/managers/SEO/render/Traits/_Properties/termCodeTrait.php
new file mode 100644
index 0000000..98b7c67
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/termCodeTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait termCodeTrait {
+	/**
+	 * @var string A code that identifies this DefinedTerm within a DefinedTermSet.
+	 */
+	protected string $termCode;
+
+	public function getTermCode():?string
+	{
+		return $this->termCode??null;
+	}
+	public function setTermCode(string $termCode):void
+	{
+		$this->termCode = $termCode;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/termsOfServiceTrait.php b/inc/managers/SEO/render/Traits/_Properties/termsOfServiceTrait.php
new file mode 100644
index 0000000..7a7109c
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/termsOfServiceTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait termsOfServiceTrait {
+	/**
+	 * @var string Human-readable terms of service documentation.
+	 */
+	protected string $termsOfService;
+
+	public function getTermsOfService():?string
+	{
+		return $this->termsOfService??null;
+	}
+	public function setTermsOfService(string $termsOfService):void
+	{
+		$this->termsOfService = $termsOfService;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/textTrait.php b/inc/managers/SEO/render/Traits/_Properties/textTrait.php
new file mode 100644
index 0000000..9a091ea
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/textTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait textTrait {
+	/**
+	 * @var string The textual content of this CreativeWork.
+	 */
+	protected string $text;
+
+	public function getText():?string
+	{
+		return $this->text??null;
+	}
+	public function setText(string $text):void
+	{
+		$this->text = $text;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/thumbnailTrait.php b/inc/managers/SEO/render/Traits/_Properties/thumbnailTrait.php
new file mode 100644
index 0000000..c07ab9b
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/thumbnailTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\MediaObject\ImageObject;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait thumbnailTrait {
+	/**
+	 * @var ImageObject Thumbnail image for an image or video.
+	 */
+	protected ImageObject $thumbnail;
+
+	public function getThumbnail():?ImageObject
+	{
+		return $this->thumbnail??null;
+	}
+	public function setThumbnail(ImageObject $thumbnail):void
+	{
+		$this->thumbnail = $thumbnail;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/thumbnailUrlTrait.php b/inc/managers/SEO/render/Traits/_Properties/thumbnailUrlTrait.php
new file mode 100644
index 0000000..bf17d25
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/thumbnailUrlTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait thumbnailUrlTrait {
+	/**
+	 * @var string A thumbnail image relevant to the Thing.
+	 */
+	protected string $thumbnailUrl;
+
+	public function getThumbnailUrl():?string
+	{
+		return $this->thumbnailUrl??null;
+	}
+	public function setThumbnailUrl(string $thumbnailUrl):void
+	{
+		$this->thumbnailUrl = $thumbnailUrl;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/timeRequiredTrait.php b/inc/managers/SEO/render/Traits/_Properties/timeRequiredTrait.php
new file mode 100644
index 0000000..232fa38
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/timeRequiredTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Quantity\Duration;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait timeRequiredTrait {
+	/**
+	 * @var Duration Approximate or typical time it usually takes to work with or through the content of this work for the typical or target audience.
+	 */
+	protected Duration $timeRequired;
+
+	public function getTimeRequired():?Duration
+	{
+		return $this->timeRequired??null;
+	}
+	public function setTimeRequired(Duration $timeRequired):void
+	{
+		$this->timeRequired = $timeRequired;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/totalPriceTrait.php b/inc/managers/SEO/render/Traits/_Properties/totalPriceTrait.php
new file mode 100644
index 0000000..7861372
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/totalPriceTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait totalPriceTrait {
+	/**
+	 * @var string The total price for the reservation or ticket, including applicable taxes, shipping, etc.
+	 */
+	protected string $totalPrice;
+
+	public function getTotalPrice():?string
+	{
+		return $this->totalPrice??null;
+	}
+	public function setTotalPrice(string $totalPrice):void
+	{
+		$this->totalPrice = $totalPrice;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/totalTimeTrait.php b/inc/managers/SEO/render/Traits/_Properties/totalTimeTrait.php
new file mode 100644
index 0000000..02bc8a5
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/totalTimeTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Quantity\Duration;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait totalTimeTrait {
+	/**
+	 * @var Duration The total time required to perform instructions or a direction (including time to prepare the supplies), in ISO 8601 duration format.
+	 */
+	protected Duration $totalTime;
+
+	public function getTotalTime():?Duration
+	{
+		return $this->totalTime??null;
+	}
+	public function setTotalTime(Duration $totalTime):void
+	{
+		$this->totalTime = $totalTime;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/transFatContentTrait.php b/inc/managers/SEO/render/Traits/_Properties/transFatContentTrait.php
new file mode 100644
index 0000000..8e1ce81
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/transFatContentTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait transFatContentTrait {
+	/**
+	 * @var int|float The number of grams of trans fat
+	 */
+	protected int|float $transFatContent;
+
+	public function getTransFatContent():int|float|null
+	{
+		return $this->transFatContent??null;
+	}
+	public function setTransFatContent(int|float $transFatContent):void
+	{
+		$this->transFatContent = $transFatContent;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/transcriptTrait.php b/inc/managers/SEO/render/Traits/_Properties/transcriptTrait.php
new file mode 100644
index 0000000..084efde
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/transcriptTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait transcriptTrait {
+	/**
+	 * @var string If this MediaObject is an AudioObject or VideoObject, the transcript of that object.
+	 */
+	protected string $transcript;
+
+	public function getTranscript():?string
+	{
+		return $this->transcript??null;
+	}
+	public function setTranscript(string $transcript):void
+	{
+		$this->transcript = $transcript;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/translationOfWorkTrait.php b/inc/managers/SEO/render/Traits/_Properties/translationOfWorkTrait.php
new file mode 100644
index 0000000..e710ed8
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/translationOfWorkTrait.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait translationOfWorkTrait {
+	/**
+	 * @var CreativeWork The work that this work has been translated from. E.g. 物种起源 is a translationOf “On the Origin of Species”.
+	 * Inverse property: workTranslation
+	 */
+	protected CreativeWork $translationOfWork;
+
+	public function getTranslationOfWork():?CreativeWork
+	{
+		return $this->translationOfWork??null;
+	}
+	public function setTranslationOfWork(CreativeWork $translationOfWork):void
+	{
+		$this->translationOfWork = $translationOfWork;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/translatorTrait.php b/inc/managers/SEO/render/Traits/_Properties/translatorTrait.php
new file mode 100644
index 0000000..deb47f6
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/translatorTrait.php
@@ -0,0 +1,32 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Thing\Person\Person;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait translatorTrait {
+	use arrayHelper;
+	/**
+	 * @var Organization|Person|array Organization or person who adapts a creative work to different languages, regional differences and technical requirements of a target market, or that translates during some event.
+	 */
+	protected Organization|Person|array $translator;
+
+	public function getTranslator():Organization|Person|array|null
+	{
+		return $this->translator??null;
+	}
+	public function setTranslator(Organization|Person|array $translator):void
+	{
+		if (is_array($translator)) {
+			$translator = $this->mixedArray('translator', $translator, [
+				'JVBase\managers\SEO\render\Thing\Organization\Organization',
+				'JVBase\managers\SEO\render\Thing\Person\Person'
+			]);
+		}
+		$this->translator = $translator;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/typeOfGoodTrait.php b/inc/managers/SEO/render/Traits/_Properties/typeOfGoodTrait.php
new file mode 100644
index 0000000..3a5a931
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/typeOfGoodTrait.php
@@ -0,0 +1,32 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Service;
+use JVBase\managers\SEO\render\Thing\Product\Product;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait typeOfGoodTrait {
+	use arrayHelper;
+	/**
+	 * @var Product|Service|array The product that this structured value is referring to.
+	 */
+	protected Product|Service|array $typeOfGood;
+
+	public function getTypeOfGood():Product|Service|array|null
+	{
+		return $this->typeOfGood??null;
+	}
+	public function setTypeOfGood(Product|Service|array $typeOfGood):void
+	{
+		if (is_array($typeOfGood)) {
+			$typeOfGood = $this->mixedArray('typeOfGood', $typeOfGood, [
+				'JVBase\managers\SEO\render\Thing\Intangible\Service',
+				'JVBase\managers\SEO\render\Thing\Product\Product'
+			]);
+		}
+		$this->typeOfGood = $typeOfGood;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/typicalAgeRangeTrait.php b/inc/managers/SEO/render/Traits/_Properties/typicalAgeRangeTrait.php
new file mode 100644
index 0000000..15e2989
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/typicalAgeRangeTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait typicalAgeRangeTrait {
+	/**
+	 * @var string The typical expected age range, e.g. '7-9', '11-'.
+	 */
+	protected string $typicalAgeRange;
+
+	public function getTypicalAgeRange():?string
+	{
+		return $this->typicalAgeRange??null;
+	}
+	public function setTypicalAgeRange(string $typicalAgeRange):void
+	{
+		$this->typicalAgeRange = $typicalAgeRange;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/unitCodeTrait.php b/inc/managers/SEO/render/Traits/_Properties/unitCodeTrait.php
new file mode 100644
index 0000000..5954420
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/unitCodeTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait unitCodeTrait {
+	/**
+	 * @var string The unit of measurement given using the UN/CEFACT Common Code (3 characters) or a URL. Other codes than the UN/CEFACT Common Code may be used with a prefix followed by a colon.
+	 */
+	protected string $unitCode;
+
+	public function getUnitCode():?string
+	{
+		return $this->unitCode??null;
+	}
+	public function setUnitCode(string $unitCode):void
+	{
+		$this->unitCode = $unitCode;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/unitTextTrait.php b/inc/managers/SEO/render/Traits/_Properties/unitTextTrait.php
new file mode 100644
index 0000000..924b10c
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/unitTextTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait unitTextTrait {
+	/**
+	 * @var string A string or text indicating the unit of measurement. Useful if you cannot provide a standard unit code for unitCode.
+	 */
+	protected string $unitText;
+
+	public function getUnitText():?string
+	{
+		return $this->unitText??null;
+	}
+	public function setUnitText(string $unitText):void
+	{
+		$this->unitText = $unitText;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/unsaturatedFatContentTrait.php b/inc/managers/SEO/render/Traits/_Properties/unsaturatedFatContentTrait.php
new file mode 100644
index 0000000..a92af77
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/unsaturatedFatContentTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait unsaturatedFatContentTrait {
+	/**
+	 * @var int|float The number of unsaturatedFatContent.
+	 */
+	protected int|float $unsaturatedFatContent;
+
+	public function getUnsaturatedFatContent():int|float|null
+	{
+		return $this->unsaturatedFatContent??null;
+	}
+	public function setUnsaturatedFatContent(int|float $unsaturatedFatContent):void
+	{
+		$this->unsaturatedFatContent = $unsaturatedFatContent;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/urlTemplateTrait.php b/inc/managers/SEO/render/Traits/_Properties/urlTemplateTrait.php
new file mode 100644
index 0000000..164f7b7
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/urlTemplateTrait.php
@@ -0,0 +1,22 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait urlTemplateTrait {
+	/**
+	 * @var string A URL template (RFC 6570)that will be used to construct the target of the execution of the action.
+	 * Example: https://www.example.com/checkout?items={VARIANT_ID_1}:{Quantity_1},{VARIANT_ID_2}:{Quantity_2}&discount={DISCOUNT_CODE}&store_id={pickup_store_id}
+	 */
+	protected string $urlTemplate;
+
+	public function getUrlTemplate():?string
+	{
+		return $this->urlTemplate??null;
+	}
+	public function setUrlTemplate(string $urlTemplate):void
+	{
+		$this->urlTemplate = $urlTemplate;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/urlTrait.php b/inc/managers/SEO/render/Traits/_Properties/urlTrait.php
new file mode 100644
index 0000000..2cc99a4
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/urlTrait.php
@@ -0,0 +1,34 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait urlTrait {
+	/**
+	 * @var string URL of the item.
+	 */
+	protected string $url;
+
+	public function getUrl():?string
+	{
+		return $this->url??null;
+	}
+	public function setUrl(string $url):void
+	{
+		if (!filter_var($url, FILTER_VALIDATE_URL)) {
+			error_log('[SEO]Not a valid url: '.$url);
+			return;
+		}
+		$this->url = $url;
+	}
+
+	public function getUrlFieldConfig():array
+	{
+		return [
+			'type'	=> 'url',
+			'label'	=> 'URL',
+			'hint'	=> 'The main url of this Thing.'
+		];
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/userInteractionCountTrait.php b/inc/managers/SEO/render/Traits/_Properties/userInteractionCountTrait.php
new file mode 100644
index 0000000..f653b50
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/userInteractionCountTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait userInteractionCountTrait {
+	/**
+	 * @var int The number of interactions for the CreativeWork using the WebSite or SoftwareApplication.
+	 */
+	protected int $userInteractionCount;
+
+	public function getUserInteractionCount():?int
+	{
+		return $this->userInteractionCount??null;
+	}
+	public function setUserInteractionCount(int $userInteractionCount):void
+	{
+		$this->userInteractionCount = $userInteractionCount;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/validFromTrait.php b/inc/managers/SEO/render/Traits/_Properties/validFromTrait.php
new file mode 100644
index 0000000..fd72b5b
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/validFromTrait.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\DataType\Date;
+use JVBase\managers\SEO\render\DataType\DateTime;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait validFromTrait {
+	/**
+	 * @var Date|DateTime The date when the item becomes valid
+	 */
+	protected Date|DateTime $validFrom;
+
+	public function getValidFrom():Date|DateTime|null
+	{
+		return $this->validFrom??null;
+	}
+	public function setValidFrom(Date|DateTime $validFrom):void
+	{
+		$this->validFrom = $validFrom;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/validInTrait.php b/inc/managers/SEO/render/Traits/_Properties/validInTrait.php
new file mode 100644
index 0000000..8525170
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/validInTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Place\AdministrativeArea\AdministrativeArea;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait validInTrait {
+	use arrayHelper;
+	/**
+	 * @var AdministrativeArea|array The geographic area where the item is valid. Applies for example to a Permit, a Certification, or an EducationalOccupationalCredential.
+	 */
+	protected AdministrativeArea|array $validIn;
+
+	public function getValidIn():AdministrativeArea|array|null
+	{
+		return $this->validIn??null;
+	}
+	public function setValidIn(AdministrativeArea|array $validIn):void
+	{
+		if (is_array($validIn)) {
+			$validIn = $this->classArray('validIn', $validIn, 'JVBase\managers\SEO\render\Thing\Place\AdministrativeArea\AdministrativeArea');
+		}
+		$this->validIn = $validIn;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/validThroughTrait.php b/inc/managers/SEO/render/Traits/_Properties/validThroughTrait.php
new file mode 100644
index 0000000..3e29049
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/validThroughTrait.php
@@ -0,0 +1,25 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\DataType\Date;
+use JVBase\managers\SEO\render\DataType\DateTime;
+use JVBase\managers\SEO\render\DataType\Time;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait validThroughTrait {
+	/**
+	 * @var Date|DateTime The date when the item becomes valid.
+	 */
+	protected Date|DateTime $validThrough;
+
+	public function getValidThrough():Date|DateTime|null
+	{
+		return $this->validThrough??null;
+	}
+	public function setValidThrough(Date|DateTime $validThrough):void
+	{
+		$this->validThrough = $validThrough;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/valueReferenceTrait.php b/inc/managers/SEO/render/Traits/_Properties/valueReferenceTrait.php
new file mode 100644
index 0000000..03620dd
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/valueReferenceTrait.php
@@ -0,0 +1,27 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\DefinedTerm;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\PropertyValue;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\StructuredValue;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait valueReferenceTrait {
+	/**
+	 * @var DefinedTerm|QuantitativeValue|PropertyValue|StructuredValue|string A secondary value that provides additional information on the original value, e.g. a reference temperature or a type of measurement.
+	 * Can also be Enumeration|MeasurementTypeEnumeration|QualitativeValue|QuantitativeValue|StructuredValue
+	 */
+	protected DefinedTerm|QuantitativeValue|PropertyValue|StructuredValue|string $valueReference;
+
+	public function getValueReference():DefinedTerm|QuantitativeValue|PropertyValue|StructuredValue|string|null
+	{
+		return $this->valueReference??null;
+	}
+	public function setValueReference(DefinedTerm|QuantitativeValue|PropertyValue|StructuredValue|string $valueReference):void
+	{
+		$this->valueReference = $valueReference;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/valueTrait.php b/inc/managers/SEO/render/Traits/_Properties/valueTrait.php
new file mode 100644
index 0000000..37c6dc6
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/valueTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait valueTrait {
+	/**
+	 * @var bool|int|float|string The value of a QuantitativeValue (including Observation) or property value node.
+	 */
+	protected bool|int|float|string $value;
+
+	public function getValue():bool|int|float|string|null
+	{
+		return $this->value??null;
+	}
+	public function setValue(bool|int|float|string $value):void
+	{
+		$this->value = $value;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/variesByTrait.php b/inc/managers/SEO/render/Traits/_Properties/variesByTrait.php
new file mode 100644
index 0000000..ec02404
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/variesByTrait.php
@@ -0,0 +1,31 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\DefinedTerm;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait variesByTrait {
+	use arrayHelper;
+	/**
+	 * @var DefinedTerm|string|array Indicates the property or properties by which the variants in a ProductGroup vary, e.g. their size, color etc. Schema.org properties can be referenced by their short name e.g. "color"; terms defined elsewhere can be referenced with their URIs.
+	 */
+	protected DefinedTerm|string|array $variesBy;
+
+	public function getVariesBy():DefinedTerm|string|array|null
+	{
+		return $this->variesBy??null;
+	}
+	public function setVariesBy(DefinedTerm|string|array $variesBy):void
+	{
+		if (is_array($variesBy)) {
+			$variesBy = $this->mixedArray('variesBy', $variesBy, [
+				'string',
+				'JVBase\managers\SEO\render\Thing\Intangible\DefinedTerm'
+			]);
+		}
+		$this->variesBy = $variesBy;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/versionTrait.php b/inc/managers/SEO/render/Traits/_Properties/versionTrait.php
new file mode 100644
index 0000000..1f60ec0
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/versionTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait versionTrait {
+	/**
+	 * @var int|float|string The version of the CreativeWork embodied by a specified resource.
+	 */
+	protected int|float|string $version;
+
+	public function getVersion():int|float|string|null
+	{
+		return $this->version??null;
+	}
+	public function setVersion(int|float|string $version):void
+	{
+		$this->version = $version;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/videoFrameSizeTrait.php b/inc/managers/SEO/render/Traits/_Properties/videoFrameSizeTrait.php
new file mode 100644
index 0000000..4a4b81f
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/videoFrameSizeTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait videoFrameSizeTrait {
+	/**
+	 * @var string A videoFrameSize of the item
+	 */
+	protected string $videoFrameSize;
+
+	public function getVideoFrameSize():?string
+	{
+		return $this->videoFrameSize??null;
+	}
+	public function setVideoFrameSize(string $videoFrameSize):void
+	{
+		$this->videoFrameSize = $videoFrameSize;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/videoQualityTrait.php b/inc/managers/SEO/render/Traits/_Properties/videoQualityTrait.php
new file mode 100644
index 0000000..592e39d
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/videoQualityTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait videoQualityTrait {
+	/**
+	 * @var string The quality of the video.
+	 */
+	protected string $videoQuality;
+
+	public function getVideoQuality():?string
+	{
+		return $this->videoQuality??null;
+	}
+	public function setVideoQuality(string $videoQuality):void
+	{
+		$this->videoQuality = $videoQuality;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/videoTrait.php b/inc/managers/SEO/render/Traits/_Properties/videoTrait.php
new file mode 100644
index 0000000..a6e79cb
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/videoTrait.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\MediaObject\Clip;
+use JVBase\managers\SEO\render\Thing\CreativeWork\MediaObject\VideoObject;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait videoTrait {
+	/**
+	 * @var Clip|VideoObject An embedded video object.
+	 */
+	protected Clip|VideoObject $video;
+
+	public function getVideo():Clip|VideoObject|null
+	{
+		return $this->video??null;
+	}
+	public function setVideo(Clip|VideoObject $video):void
+	{
+		$this->video = $video;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/warrantyScopeTrait.php b/inc/managers/SEO/render/Traits/_Properties/warrantyScopeTrait.php
new file mode 100644
index 0000000..890ffd3
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/warrantyScopeTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Enumeration\WarrantyScope;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait warrantyScopeTrait {
+	/**
+	 * @var WarrantyScope The scope of the warranty promise.
+	 */
+	protected WarrantyScope $warrantyScope;
+
+	public function getWarrantyScope():?WarrantyScope
+	{
+		return $this->warrantyScope??null;
+	}
+	public function setWarrantyScope(WarrantyScope $warrantyScope):void
+	{
+		$this->warrantyScope = $warrantyScope;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/warrantyTrait.php b/inc/managers/SEO/render/Traits/_Properties/warrantyTrait.php
new file mode 100644
index 0000000..064a1ee
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/warrantyTrait.php
@@ -0,0 +1,23 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\WarrantyPromise;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait warrantyTrait {
+	/**
+	 * @var WarrantyPromise The warranty promise(s) included in the offer. Supersedes warrantyPromise.
+	 */
+	protected WarrantyPromise $warrantyPromise;
+
+	public function getWarrantyPromise():?WarrantyPromise
+	{
+		return $this->warrantyPromise??null;
+	}
+	public function setWarrantyPromise(WarrantyPromise $warrantyPromise):void
+	{
+		$this->warrantyPromise = $warrantyPromise;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/weightTrait.php b/inc/managers/SEO/render/Traits/_Properties/weightTrait.php
new file mode 100644
index 0000000..08ff9ca
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/weightTrait.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Quantity\Mass;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait weightTrait {
+	/**
+	 * @var Mass|QuantitativeValue The weight of the item.
+	 */
+	protected Mass|QuantitativeValue $weight;
+
+	public function getWeight():Mass|QuantitativeValue|null
+	{
+		return $this->weight??null;
+	}
+	public function setWeight(Mass|QuantitativeValue $weight):void
+	{
+		$this->weight = $weight;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/widthTrait.php b/inc/managers/SEO/render/Traits/_Properties/widthTrait.php
new file mode 100644
index 0000000..d23977b
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/widthTrait.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\Quantity\Distance;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\QuantitativeValue;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait widthTrait {
+	/**
+	 * @var Distance|QuantitativeValue The width of the item.
+	 */
+	protected Distance|QuantitativeValue $width;
+
+	public function getWidth():Distance|QuantitativeValue|null
+	{
+		return $this->width??null;
+	}
+	public function setWidth(Distance|QuantitativeValue $width):void
+	{
+		$this->width = $width;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/wordCountTrait.php b/inc/managers/SEO/render/Traits/_Properties/wordCountTrait.php
new file mode 100644
index 0000000..a7eca4f
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/wordCountTrait.php
@@ -0,0 +1,21 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait wordCountTrait {
+	/**
+	 * @var int The number of words in the text of the CreativeWork such as an Article, Book, etc.
+	 */
+	protected int $wordCount;
+
+	public function getWordCount():?int
+	{
+		return $this->wordCount??null;
+	}
+	public function setWordCount(int $wordCount):void
+	{
+		$this->wordCount = $wordCount;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/workExampleTrait.php b/inc/managers/SEO/render/Traits/_Properties/workExampleTrait.php
new file mode 100644
index 0000000..257bd07
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/workExampleTrait.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait workExampleTrait {
+	/**
+	 * @var CreativeWork Example/instance/realization/derivation of the concept of this creative work. E.g. the paperback edition, first edition, or e-book.
+	 * Inverse property: exampleOfWork
+	 */
+	protected CreativeWork $workExample;
+
+	public function getWorkExample():?CreativeWork
+	{
+		return $this->workExample??null;
+	}
+	public function setWorkExample(CreativeWork $workExample):void
+	{
+		$this->workExample = $workExample;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/workFeaturedTrait.php b/inc/managers/SEO/render/Traits/_Properties/workFeaturedTrait.php
new file mode 100644
index 0000000..61bcbad
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/workFeaturedTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait workFeaturedTrait {
+	use arrayHelper;
+	/**
+	 * @var CreativeWork|array A work featured in some event, e.g. exhibited in an ExhibitionEvent. Specific subproperties are available for workPerformed (e.g. a play), or a workPresented (a Movie at a ScreeningEvent).
+	 */
+	protected CreativeWork|array $workFeatured;
+
+	public function getWorkFeatured():CreativeWork|array|null
+	{
+		return $this->workFeatured??null;
+	}
+	public function setWorkFeatured(CreativeWork|array $workFeatured):void
+	{
+		if (is_array($workFeatured)) {
+			$workFeatured = $this->classArray('workFeatured', $workFeatured, 'JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork');
+		}
+		$this->workFeatured = $workFeatured;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/workPerformedTrait.php b/inc/managers/SEO/render/Traits/_Properties/workPerformedTrait.php
new file mode 100644
index 0000000..a2c87b1
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/workPerformedTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait workPerformedTrait {
+	use arrayHelper;
+	/**
+	 * @var CreativeWork|array A work performed in some event, for example a play performed in a TheaterEvent.
+	 */
+	protected CreativeWork|array $workPerformed;
+
+	public function getWorkPerformed():CreativeWork|array|null
+	{
+		return $this->workPerformed??null;
+	}
+	public function setWorkPerformed(CreativeWork|array $workPerformed):void
+	{
+		if (is_array($workPerformed)) {
+			$workPerformed = $this->classArray('workPerformed', $workPerformed, 'JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork');
+		}
+		$this->workPerformed = $workPerformed;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/workTranslationTrait.php b/inc/managers/SEO/render/Traits/_Properties/workTranslationTrait.php
new file mode 100644
index 0000000..42c25e2
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/workTranslationTrait.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait workTranslationTrait {
+	/**
+	 * @var CreativeWork A work that is a translation of the content of this work. E.g. 西遊記 has an English workTranslation “Journey to the West”, a German workTranslation “Monkeys Pilgerfahrt” and a Vietnamese translation Tây du ký bình khảo.
+	 * Inverse property: translationOfWork
+	 */
+	protected CreativeWork $workTranslation;
+
+	public function getWorkTranslation():?CreativeWork
+	{
+		return $this->workTranslation??null;
+	}
+	public function setWorkTranslation(CreativeWork $workTranslation):void
+	{
+		$this->workTranslation = $workTranslation;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/worksAtTrait.php b/inc/managers/SEO/render/Traits/_Properties/worksAtTrait.php
new file mode 100644
index 0000000..8c422ce
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/worksAtTrait.php
@@ -0,0 +1,32 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\ContactPoint;
+use JVBase\managers\SEO\render\Thing\Place\Place;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait worksAtTrait {
+	use arrayHelper;
+	/**
+	 * @var ContactPoint|Place|array A contact location for a person's place of work.
+	 */
+	protected ContactPoint|Place|array $worksAt;
+
+	public function getWorksAt():ContactPoint|Place|array|null
+	{
+		return $this->worksAt??null;
+	}
+	public function setWorksAt(ContactPoint|Place|array $worksAt):void
+	{
+		if (is_array($worksAt)) {
+			$worksAt = $this->mixedArray('worksAt', $worksAt, [
+				'JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\ContactPoint',
+				'JVBase\managers\SEO\render\Thing\Place\Place'
+			]);
+		}
+		$this->worksAt = $worksAt;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/worksForTrait.php b/inc/managers/SEO/render/Traits/_Properties/worksForTrait.php
new file mode 100644
index 0000000..31122af
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/worksForTrait.php
@@ -0,0 +1,28 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+use JVBase\managers\SEO\render\Thing\Organization\Organization;
+use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait worksForTrait {
+	use arrayHelper;
+	/**
+	 * @var Organization|array Organizations that the person works for.
+	 */
+	protected Organization|array $worksFor;
+
+	public function getWorksFor():Organization|array|null
+	{
+		return $this->worksFor??null;
+	}
+	public function setWorksFor(Organization|array $worksFor):void
+	{
+		if (is_array($this->worksFor)) {
+			$worksFor = $this->classArray('worksFor', $worksFor, 'JVBase\managers\SEO\render\Thing\Organization\Organization');
+		}
+		$this->worksFor = $worksFor;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_Properties/worstRatingTrait.php b/inc/managers/SEO/render/Traits/_Properties/worstRatingTrait.php
new file mode 100644
index 0000000..5446eb8
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_Properties/worstRatingTrait.php
@@ -0,0 +1,22 @@
+<?php
+namespace JVBase\managers\SEO\render\Traits\_Properties;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+trait worstRatingTrait {
+
+	/**
+	 * @var int The lowest value allowed in this rating system.
+	 */
+	protected int $worstRating = 1;
+
+	public function getWorstRating():?int
+	{
+		return $this->worstRating??null;
+	}
+	public function setWorstRating(int $worstRating):void
+	{
+		$this->worstRating = $worstRating;
+	}
+}
diff --git a/inc/managers/SEO/render/Traits/_setup.php b/inc/managers/SEO/render/Traits/_setup.php
new file mode 100644
index 0000000..dd5bc0c
--- /dev/null
+++ b/inc/managers/SEO/render/Traits/_setup.php
@@ -0,0 +1,7 @@
+<?php
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Helpers/_setup.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_Properties/_setup.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/ThingSchema.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/Organization/_setup.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/Place/_setup.php');
+
diff --git a/inc/managers/SEO/render/_setup.php b/inc/managers/SEO/render/_setup.php
new file mode 100644
index 0000000..687844c
--- /dev/null
+++ b/inc/managers/SEO/render/_setup.php
@@ -0,0 +1,5 @@
+<?php
+require(JVB_DIR . '/inc/managers/SEO/render/Traits/_setup.php');
+require(JVB_DIR . '/inc/managers/SEO/render/DataType/_setup.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Thing/_setup.php');
+require(JVB_DIR . '/inc/managers/SEO/render/SchemaOutput.php');
diff --git a/inc/managers/SEO/render/seo/_setup.php b/inc/managers/SEO/render/seo/_setup.php
new file mode 100644
index 0000000..cd1edf4
--- /dev/null
+++ b/inc/managers/SEO/render/seo/_setup.php
@@ -0,0 +1,5 @@
+<?php
+require(JVB_DIR . '/inc/managers/SEO/render/Output/Archive.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Output/Meta.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Output/Resolver.php');
+require(JVB_DIR . '/inc/managers/SEO/render/Output/Schema.php');
diff --git a/inc/managers/SEO/schemas/resolvers/PersonResolver.php b/inc/managers/SEO/schemas/resolvers/PersonResolver.php
index cd9d95c..efce13f 100644
--- a/inc/managers/SEO/schemas/resolvers/PersonResolver.php
+++ b/inc/managers/SEO/schemas/resolvers/PersonResolver.php
@@ -4,6 +4,7 @@
 use JVBase\managers\SEO\schemas\SchemaDefinition;
 use JVBase\managers\SEO\SchemaReferenceBuilder;
 use JVBase\meta\Meta;
+use JVBase\registrar\Registrar;
 
 if (!defined('ABSPATH')) {
 	exit;
@@ -50,17 +51,13 @@
 	 */
 	private function buildWorksFor(int $userId): ?array
 	{
-		if (!defined('JVB_TAXONOMY')) {
+		$contentTypes = Registrar::getFeatured('is_content', 'term');
+		if (empty($contentTypes)) {
 			return null;
 		}
 
-		// Find taxonomies that represent organizations (is_content taxonomies)
-		foreach (JVB_TAXONOMY as $slug => $config) {
-			if (empty($config['is_content'])) {
-				continue;
-			}
-
-			$fullTax = BASE . $slug;
+		foreach ($contentTypes as $content) {
+			$fullTax = jvbCheckBase($content);
 			$terms = wp_get_object_terms($userId, $fullTax);
 
 			if (is_wp_error($terms) || empty($terms)) {
@@ -71,9 +68,8 @@
 			$term = $terms[0];
 
 			return SchemaReferenceBuilder::build(
-				$term->term_id,
 				'term',
-				null
+				$term->term_id
 			);
 		}
 
@@ -85,17 +81,24 @@
 	 */
 	private function buildWorkExamples(int $userId, int $limit = 5): array
 	{
-		if (!defined('JVB_CONTENT')) {
+		$role = jvbUserRole($userId);
+		$registrar = Registrar::getInstance($role);
+
+		if (!$registrar){
+			return [];
+		}
+		$types = $registrar->getCreatable();
+		if (empty($types)){
 			return [];
 		}
 
 		$examples = [];
 
-		foreach (JVB_CONTENT as $slug => $config) {
-			$fullType = BASE . $slug;
+		foreach ($types as $slug) {
+			$type = jvbCheckBase($slug);
 
 			$posts = get_posts([
-				'post_type'      => $fullType,
+				'post_type'      => $type,
 				'author'         => $userId,
 				'posts_per_page' => $limit,
 				'post_status'    => 'publish',
@@ -106,7 +109,7 @@
 				continue;
 			}
 
-			$refs = SchemaReferenceBuilder::buildMultiple($posts, 'post', null, true);
+			$refs = SchemaReferenceBuilder::buildMultiple('post', $posts, null, true);
 
 			if (!empty($refs)) {
 				$examples = array_merge($examples, $refs);
diff --git a/inc/managers/SEO/schemas/resolvers/VisualArtworkResolver.php b/inc/managers/SEO/schemas/resolvers/VisualArtworkResolver.php
index 3ec72a3..f3272a3 100644
--- a/inc/managers/SEO/schemas/resolvers/VisualArtworkResolver.php
+++ b/inc/managers/SEO/schemas/resolvers/VisualArtworkResolver.php
@@ -5,6 +5,7 @@
 use JVBase\managers\SEO\SchemaFieldHelpers;
 use JVBase\managers\SEO\SchemaReferenceBuilder;
 use JVBase\meta\Meta;
+use JVBase\registrar\Registrar;
 
 if (!defined('ABSPATH')) {
 	exit;
@@ -29,12 +30,12 @@
 			return $fields;
 		}
 
-		$contentConfig = $this->getContentConfig($definition->contentType);
-
-		if (empty($contentConfig)) {
+		$registrar = Registrar::getInstance($definition->contentType);
+		if (!$registrar){
 			return $fields;
 		}
 
+
 		// Derive artform from style taxonomy terms
 		$artform = $this->deriveFromTaxonomy($definition->objectId, $contentConfig, 'style');
 		if (!empty($artform)) {
@@ -130,17 +131,4 @@
 		return array_unique($keywords);
 	}
 
-	/**
-	 * Get content config from JVB_CONTENT constant.
-	 */
-	private function getContentConfig(?string $contentType): array
-	{
-		if (!$contentType || !defined('JVB_CONTENT')) {
-			return [];
-		}
-
-		$slug = jvbNoBase($contentType);
-
-		return JVB_CONTENT[$slug] ?? [];
-	}
 }
diff --git a/inc/managers/_setup.php b/inc/managers/_setup.php
index 50f6299..d29be0c 100644
--- a/inc/managers/_setup.php
+++ b/inc/managers/_setup.php
@@ -2,6 +2,7 @@
 
 use JVBase\managers\Cache;
 use JVBase\managers\IconsManager;
+use JVBase\registrar\Registrar;
 use JVBase\utility\Features;
 
 require(JVB_DIR . '/inc/managers/ScriptLoader.php');
@@ -64,7 +65,7 @@
 	require(JVB_DIR . '/inc/managers/NotificationManager.php');
 }
 
-if (Features::forMembership()->has('forum') && Features::anyTaxonomyHas('is_content')) {
+if (Features::forMembership()->has('forum') && !empty(Registrar::getFeatured('is_content', 'term'))) {
 	require(JVB_DIR . '/inc/managers/NewsRelationships.php');
 }
 
diff --git a/inc/managers/queue/Queue.php b/inc/managers/queue/Queue.php
index 28abc4e..d93078b 100644
--- a/inc/managers/queue/Queue.php
+++ b/inc/managers/queue/Queue.php
@@ -4,6 +4,7 @@
 	exit;
 }
 
+use JVBase\managers\CustomTable;
 use WP_Error;
 use WP_REST_Request;
 use WP_REST_Response;
@@ -18,6 +19,7 @@
 
 	public function __construct()
 	{
+		$this->defineTables();
 		$this->storage = new Storage();
 		$this->registry = new TypeRegistry();
 		$this->locker = new Locker();
@@ -39,6 +41,84 @@
 		add_filter(BASE.'admin_action_filter', [$this, 'adminActionFilter'], 10, 3);
 	}
 
+	public static function defineTables():void
+	{
+		$queue = CustomTable::for('_operation_queue');
+		$queue->setColumns([
+			'id'			=> 'VARCHAR(64) NOT NULL',
+			'type'			=> 'VARCHAR(50) NOT NULL',
+			'user_id'		=> $queue->getUserIDType().' NOT NULL',
+
+			'request_data'	=> 'JSON NOT NULL CHECK (JSON_VALID(request_data))',
+
+			'total_items'	=> 'INT(11) NOT NULL DEFAULT 1',
+			'processed_items'	=> 'INT(11) DEFAULT 0',
+			'failed_items'	=> 'JSON',
+
+			'priority'		=> 'ENUM(\'high\',\'normal\',\'low\') DEFAULT \'normal\'',
+			'state'			=> 'ENUM(\'pending\', \'scheduled\', \'processing\', \'completed\') DEFAULT \'pending\'',
+			'outcome'		=> 'ENUM(\'pending\', \'success\',\'partial\',\'merged\',\'failed\',\'failed_permanent\') DEFAULT \'pending\'',
+
+			'retries'		=> 'INT(11) DEFAULT 0',
+			'last_error_hash'=> 'CHAR(32) DEFAULT NULL',
+			'error_message'	=> 'TEXT',
+
+			'scheduled_at'	=> 'DATETIME DEFAULT NULL',
+			'started_at'	=> 'DATETIME DEFAULT CURRENT_TIMESTAMP',
+			'completed_at'	=> 'DATETIME DEFAULT NULL',
+
+			'metadata'		=> 'JSON DEFAULT NULL',
+			'result'		=> 'JSON',
+			'dependencies'	=> 'JSON',
+			'merged_into'	=> 'VARCHAR(64) DEFAULT NULL',
+
+			'user_dismissed'=> 'tinyint(1) DEFAULT 0',
+			'created_at'	=> 'DATETIME DEFAULT CURRENT_TIMESTAMP',
+			'updated_at'	=> 'DATETIME DEFAULT CURRENT_TIMESTAMP',
+		]);
+
+		$queue->setKeys([
+			['key' => 'PRIMARY', 'value' => 'id'],
+			'`idx_run_queue` (`state`, `priority`, `scheduled_at`)',
+			'`idx_user_ops` (`user_id`, `state`)',
+			'`idx_user_type_pending` (`user_id`, `type`, `state`)',
+			'`idx_completed_at` (`completed_at`)',
+			'`idx_processing_stuck` (`state`, `started_at`)'
+		]);
+
+		$queue->defineTable();
+
+		$stats = CustomTable::for('stats__operation_queue');
+		$stats->setColumns([
+			'id'	=> 'BIGINT unsigned AUTO_INCREMENT',
+			'date'	=> 'DATE NOT NULL',
+			'type'	=> 'VARCHAR(50) NOT NULL',
+
+			'total_operations'		=> 'INT NOT NULL DEFAULT 0',
+			'successful_operations'	=> 'INT NOT NULL DEFAULT 0',
+			'partial_operations'	=> 'INT NOT NULL DEFAULT 0',
+			'failed_operations'		=> 'INT NOT NULL DEFAULT 0',
+			'failed_permanent_operations'=> 'INT NOT NULL DEFAULT 0',
+			'total_items_processed'	=> 'INT NOT NULL DEFAULT 0',
+
+			'average_duration'		=> 'FLOAT DEFAULT NULL',
+			'max_duration'			=> 'INT DEFAULT NULL',
+			'peak_queue_size'		=> 'INT NOT NULL DEFAULT 0',
+			'peak_memory_usage'		=> 'INT DEFAULT NULL',
+			'peak_cpu_usage'		=> 'FLOAT DEFAULT NULL',
+
+			'created_at'			=> 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP',
+		]);
+
+		$stats->setKeys([
+			['key' => 'PRIMARY', 'value' => 'id'],
+			['key' => 'UNIQUE', 'value' => '(`date`, `type`)'],
+			'`date_idx` (`date`)',
+			'`type_idx` (`type`)'
+		]);
+		$stats->defineTable();
+	}
+
 	/**
 	 * Access type registry for registering operation configs
 	 */
diff --git a/inc/managers/queue/executors/ContentExecutor.php b/inc/managers/queue/executors/ContentExecutor.php
index c6d99af..e8b6e72 100644
--- a/inc/managers/queue/executors/ContentExecutor.php
+++ b/inc/managers/queue/executors/ContentExecutor.php
@@ -3,7 +3,7 @@
 
 use JVBase\managers\queue\{Executor, Operation, Progress, Result, Storage};
 use JVBase\meta\Meta;
-use JVBase\utility\Features;
+use JVBase\registrar\Registrar;
 use Exception;
 
 if (!defined('ABSPATH')) {
@@ -116,11 +116,12 @@
 				}
 
 				$this->savePostFields((int)$id, $postData);
-
-				if (Features::forContent($content)->has('is_timeline')) {
+				$registrar = Registrar::getInstance($content);
+				if ($registrar && $registrar->hasFeature('is_timeline')) {
 					$post = get_post((int)$id);
 					$parentId = $post->post_parent > 0 ? $post->post_parent : $post->ID;
-					$sharedFields = array_keys(array_filter(JVB_CONTENT[$content]['fields'], function ($field) {
+					$fields = $registrar->getFields();
+					$sharedFields = array_keys(array_filter($fields, function ($field) {
 						return !array_key_exists('for_all', $field) || !$field['for_all'];
 					}));
 
@@ -195,7 +196,7 @@
 	private function savePostFields(int $postId, array $postData): bool
 	{
 		$content = $postData['content'] ?? '';
-		$fields = jvbGetFields($content);
+		$fields = Registrar::getFieldsFor($content);
 
 		$allowedFields = array_filter($postData, function ($key) use ($fields) {
 			return array_key_exists($key, $fields);
diff --git a/inc/managers/queue/executors/ContentTermExecutor.php b/inc/managers/queue/executors/ContentTermExecutor.php
index bac1179..e8f4494 100644
--- a/inc/managers/queue/executors/ContentTermExecutor.php
+++ b/inc/managers/queue/executors/ContentTermExecutor.php
@@ -8,7 +8,7 @@
 use JVBase\managers\queue\Result;
 use JVBase\managers\RoleManager;
 use JVBase\meta\Meta;
-use JVBase\utility\Features;
+use JVBase\registrar\Registrar;
 use Exception;
 
 if (!defined('ABSPATH')) {
@@ -35,10 +35,10 @@
 	public function execute(Operation $operation, Progress $progress): Result
 	{
 		// Extract taxonomy from operation type (e.g., "shop_update" -> "shop")
-		$parts = explode('_', $operation->type);
-		$taxonomy = $parts[0] ?? '';
-
-		if (!$taxonomy || !isset(JVB_TAXONOMY[$taxonomy])) {
+		$data= $operation->requestData;
+		$taxonomy = $data['taxonomy']??false;
+		$registrar = $taxonomy? Registrar::getInstance($taxonomy) : false;
+		if (!$taxonomy || !$registrar) {
 			return Result::fail("Invalid taxonomy: {$taxonomy}");
 		}
 
@@ -73,7 +73,7 @@
 			unset($data['term_id']);
 
 			// Filter to only allowed fields
-			$allowed = jvbGetFields($taxonomy, 'term');
+			$allowed = Registrar::getFieldsFor($taxonomy);
 			$setData = array_filter(
 				$data,
 				fn($key) => array_key_exists($key, $allowed),
@@ -128,7 +128,8 @@
 		}
 
 		// Check if tracking enabled
-		if (!Features::forTaxonomy($taxonomy)->has('track_changes')) {
+		$registrar = Registrar::getInstance($taxonomy);
+		if ($registrar && !$registrar->hasFeature('track_changes')) {
 			return Result::fail('Member tracking not enabled for ' . $taxonomy);
 		}
 
@@ -173,15 +174,18 @@
 	 */
 	protected function addMember(int $userID, int $termID, string $taxonomy): Result
 	{
-		$config = JVB_TAXONOMY[$taxonomy] ?? [];
-		$content = $config['for_content'] ?? [];
+		$registrar = Registrar::getInstance($taxonomy);
+		if (!$registrar) {
+			return Result::fail('No content registered');
+		}
 
-		if (empty($content)) {
+		$forContent = $registrar->registrar->for;
+		if (empty($forContent)) {
 			return Result::fail('No content types configured for ' . $taxonomy);
 		}
 
 		// Get table name (e.g., "history_artist_shop")
-		$contentType = $content[0]; // Use first content type
+		$contentType = is_array($forContent) ? $forContent[0] : $forContent; // Use first content type
 		$tableName = "history_{$contentType}_{$taxonomy}";
 		$table = CustomTable::for($tableName);
 
@@ -235,9 +239,6 @@
 				throw new Exception('Failed to set taxonomy term: ' . $termResult->get_error_message());
 			}
 
-			// Clear cache
-			JVB()->cache()->for($taxonomy)->delete("{$taxonomy}_{$termID}_members");
-
 			// Notify term managers
 			$this->notifyTermManagers($termID, $userID, $taxonomy, 'member_added');
 
@@ -254,15 +255,19 @@
 	 */
 	protected function removeMember(int $userID, int $termID, string $taxonomy): Result
 	{
-		$config = JVB_TAXONOMY[$taxonomy] ?? [];
-		$content = $config['for_content'] ?? [];
+		$registrar = Registrar::getInstance($taxonomy);
+		if (!$registrar) {
+			return Result::fail('No content registered');
+		}
+		$forContent = $registrar->registrar->for;
 
-		if (empty($content)) {
+		if (empty($forContent)) {
 			return Result::fail('No content types configured for ' . $taxonomy);
 		}
 
 		// Get table name
-		$contentType = $content[0];
+		$contentType = is_array($forContent) ? $forContent[0] : $forContent;
+
 		$tableName = "history_{$contentType}_{$taxonomy}";
 		$table = CustomTable::for($tableName);
 
@@ -296,9 +301,6 @@
 				throw new Exception('Failed to remove taxonomy term: ' . $termResult->get_error_message());
 			}
 
-			// Clear cache
-			JVB()->cache()->for($taxonomy)->delete("{$taxonomy}_{$termID}_members");
-
 			// Notify term managers
 			$this->notifyTermManagers($termID, $userID, $taxonomy, 'member_removed');
 
diff --git a/inc/managers/queue/executors/UploadExecutor.php b/inc/managers/queue/executors/UploadExecutor.php
index cc828d5..91b4741 100644
--- a/inc/managers/queue/executors/UploadExecutor.php
+++ b/inc/managers/queue/executors/UploadExecutor.php
@@ -6,6 +6,7 @@
 use JVBase\managers\UploadManager;
 use JVBase\meta\Meta;
 use Exception;
+use JVBase\registrar\Registrar;
 use JVBase\utility\Features;
 
 if (!defined('ABSPATH')) {
@@ -384,7 +385,8 @@
 		}
 
 		$content = jvbCheckBase($data['content']);
-		if (Features::forContent($data['content'])->has('is_timeline')) {
+		$registrar = Registrar::getInstance($data['content']);
+		if ($registrar && $registrar->hasFeature('is_timeline')) {
 			return $this->processTimelineUploads($operation, $data, $progress, $all_uploads);
 		}
 
@@ -438,14 +440,11 @@
 
 	protected function createPostFromGroup(array $post, int $index, string $content, array $uploads, Operation $op):array|false
 	{
-		$config = JVB_CONTENT[jvbNoBase($content)]??false;
-		if (!$config) {
-			throw new Exception('No config found for content: '.$content.'.');
-		}
+		$registrar = Registrar::getInstance($content);
 
 		$post_title = array_key_exists('post_title', $post['fields'])
 			? sanitize_text_field($post['fields']['post_title'])
-			: 'New '. $config['singular'].' '.($index + 1);
+			: ($registrar ? 'New '. $registrar->getSingular().' '.($index + 1) : 'New Item '.($index + 1));
 
 		$post_excerpt = array_key_exists('post_excerpt', $post['fields'])
 			? sanitize_textarea_field($post['fields']['post_excerpt'])
@@ -489,7 +488,7 @@
 
 		if (!empty($gallery)) {
 			$meta = Meta::forPost($ID);
-			$fields = jvbGetFields($content, 'post');
+			$fields = Registrar::getFieldsFor($content);
 			//add images to first found gallery field
 			$found = false;
 			foreach ($fields as $name =>$config) {
@@ -521,9 +520,10 @@
 		$errors = [];
 
 		$content = jvbCheckBase($data['content']);
-		$config = Features::getConfig($content);
+		$registrar = Registrar::getInstance($data['content']);
 
-		$defaultTitle = 'New '.$config['singular']. ' ';
+
+		$defaultTitle = ($registrar) ? 'New '.$registrar->getSingular(). ' ' : 'New Item ';
 		foreach($data['posts'] as $index => $post) {
 			try {
 				$title = array_key_exists('post_title', $post['fields'])
diff --git a/inc/meta/Form.php b/inc/meta/Form.php
index 1d3db3f..57bdd85 100644
--- a/inc/meta/Form.php
+++ b/inc/meta/Form.php
@@ -2,6 +2,7 @@
 namespace JVBase\meta;
 
 use DateTime;
+use JVBase\registrar\Registrar;
 
 if (!defined('ABSPATH')) {
 	exit;
@@ -674,16 +675,9 @@
 			$content = $config['content'];
 
 			$config['data']['content'] = $content;
-			$plural =
-				JVB_CONTENT[$content]['plural']
-				?? JVB_TAXONOMY[$content]['plural']
-				?? JVB_USER[$content]['plural']
-				?? str_replace('_', ' ', $content) . 's';
-
-			$singular = JVB_CONTENT[$content]['singular']
-				?? JVB_TAXONOMY[$content]['singular']
-				?? JVB_USER[$content]['singular']
-				?? str_replace('_', ' ',$config['content']);
+			$registrar = Registrar::getInstance($content);
+			$plural = $registrar->getPlural()??str_replace('_', ' ', $content).'s';
+			$singular = $registrar->getSingular()??str_replace('_', ' ', $content);
 		}
 		if ($config['limit'] > 0) {
 			$config['data']['max-files'] = $config['limit'];
@@ -1185,12 +1179,11 @@
 			'type'		=> $config['subtype'],
 		], $config);
 
-		$icon = match ($config['subtype']) {
-			'taxonomy' => JVB_TAXONOMY[$config['taxonomy']]['icon'] ?? jvbDefaultIcon(),
-			'content' => JVB_CONTENT[$config['content']]['icon'] ?? jvbDefaultIcon(),
-			'user' => JVB_USER[$config['role']]['icon'] ?? 'user',
-			default => jvbDefaultIcon(),
-		};
+		$registrar = Registrar::getInstance($config['subtype']);
+		$icon = jvbDefaultIcon();
+		if ($registrar){
+			$icon = $registrar->getIcon()??jvbDefaultIcon();
+		}
 
 		$containerId = sprintf('%s-%s-selector', $name, $config['subtype']);
 
@@ -1228,45 +1221,48 @@
 
 		protected static function getPlural(array $config):array
 		{
+			$single = $plural = '';
 			switch ($config['subtype']) {
 				case 'taxonomy':
-					if (array_key_exists($config['taxonomy'], JVB_TAXONOMY)) {
-						$single = JVB_TAXONOMY[$config['taxonomy']]['singular'];
-						$plural = JVB_TAXONOMY[$config['taxonomy']]['plural'];
+					$registrar = Registrar::getInstance($config['taxonomy']);
+					if ($registrar) {
+						$single = $registrar->getSingular();
+						$plural = $registrar->getPlural();
 					} else {
 						$taxonomy = get_taxonomy($config['taxonomy']);
-						if (!$taxonomy) {
-							return [];
+						if ($taxonomy) {
+							$single = $taxonomy->labels->singular_name;
+							$plural = $taxonomy->labels->name;
 						}
-						$single = $taxonomy->labels->singular_name;
-						$plural = $taxonomy->labels->name;
 					}
 					break;
 				case 'content':
-					if (array_key_exists($config['content'], JVB_CONTENT)) {
-						$single = JVB_CONTENT[$config['content']]['singular'];
-						$plural = JVB_CONTENT[$config['content']]['plural'];
+					$registrar = Registrar::getInstance($config['content']);
+					if ($registrar) {
+						$single = $registrar->getSingular();
+						$plural = $registrar->getPlural();
 					} else {
 						$postType = get_post_type_object($config['content']);
-						if (!$postType) {
-							return '';
+						if ($postType) {
+							$single = $postType->labels->singular_name;
+							$plural = $postType->labels->name;
 						}
-						$single = $postType->labels->singular_name;
-						$plural = $postType->labels->name;
+
 					}
 					break;
 
 				case 'user':
-					if (array_key_exists($config['user'], JVB_USER)) {
-						$single = JVB_USER[$config['user']]['singular'];
-						$plural = JVB_USER[$config['user']]['plural'];
+					$registrar = Registrar::getInstance($config['user']);
+
+					if ($registrar) {
+						$single = $registrar->getSingular();
+						$plural = $registrar->getPlural();
 					} else {
 						$user = get_role($config['user']);
-						if (!$user) {
-							return '';
+						if ($user) {
+							$single = 'User';
+							$plural = 'Users';
 						}
-						$single = 'User';
-						$plural = 'Users';
 					}
 					break;
 			}
@@ -1277,9 +1273,10 @@
 		{
 			switch ($config['subtype']) {
 				case 'taxonomy':
-					if (array_key_exists($config['taxonomy'], JVB_TAXONOMY)) {
-						$single = JVB_TAXONOMY[$config['taxonomy']]['singular'];
-						$plural = JVB_TAXONOMY[$config['taxonomy']]['plural'];
+					$registrar = Registrar::getInstance($config['taxonomy']);
+					if ($registrar) {
+						$single = $registrar->getSingular();
+						$plural = $registrar->getPlural();
 					} else {
 						$taxonomy = get_taxonomy($config['taxonomy']);
 						if (!$taxonomy) {
@@ -1296,9 +1293,10 @@
 					);
 					break;
 				case 'content':
-					if (array_key_exists($config['content'], JVB_CONTENT)) {
-						$single = JVB_CONTENT[$config['content']]['singular'];
-						$plural = JVB_CONTENT[$config['content']]['plural'];
+					$registrar = Registrar::getInstance($config['content']);
+					if ($registrar) {
+						$single = $registrar->getSingular();
+						$plural = $registrar->getPlural();
 					} else {
 						$postType = get_post_type_object($config['content']);
 						if (!$postType) {
@@ -1316,9 +1314,10 @@
 					break;
 
 				case 'user':
-					if (array_key_exists($config['user'], JVB_USER)) {
-						$single = JVB_USER[$config['user']]['singular'];
-						$plural = JVB_USER[$config['user']]['plural'];
+					$registrar = Registrar::getInstance($config['user']);
+					if ($registrar){
+						$single = $registrar->getSingular();
+						$plural = $registrar->getPlural();
 					} else {
 						$user = get_role($config['user']);
 						if (!$user) {
diff --git a/inc/meta/Meta.php b/inc/meta/Meta.php
index f7a35b4..8984345 100644
--- a/inc/meta/Meta.php
+++ b/inc/meta/Meta.php
@@ -1,6 +1,8 @@
 <?php
 namespace JVBase\meta;
 
+use JVBase\registrar\Registrar;
+
 if (!defined('ABSPATH')) {
 	exit;
 }
@@ -245,7 +247,7 @@
 			return [];
 		}
 
-		return jvbGetFields($contentType ?? 'options', $objectType);
+		return Registrar::getFieldsFor($contentType??'options');
 	}
 
 	// ─────────────────────────────────────────────────────────────
diff --git a/inc/meta/MetaManager.php b/inc/meta/MetaManager.php
index 4fe121f..7dcc0af 100644
--- a/inc/meta/MetaManager.php
+++ b/inc/meta/MetaManager.php
@@ -1,10 +1,8 @@
 <?php
 namespace JVBase\meta;
 
-use DateTime;
 use Exception;
-use JVBase\utility\Features;
-use WP_Post;
+use JVBase\registrar\Registrar;
 
 if (!defined('ABSPATH')) {
     exit; // Exit if accessed directly
@@ -71,7 +69,8 @@
 				case 'post':
 					$this->data = get_post((string)$ID);
 					$this->content = jvbNoBase($this->data->post_type);
-					$this->isTimeline = Features::forContent($this->content)->has('is_timeline');
+					$registrar = Registrar::getInstance($this->content);
+					$this->isTimeline = $registrar && $registrar->hasFeature('is_timeline');
 					break;
 				case 'term':
 					$this->data = get_term($ID);
@@ -538,13 +537,13 @@
 				$type = jvbUserRole((int)$this->object_id);
 				break;
 			case 'options':
-				return jvbGetFields('options');
+				return Registrar::getFieldsFor('options');
 		}
 		if (!$type) {
 			return [];
 		}
 
-		$this->fields = jvbGetFields($type, $this->object_type);
+		$this->fields = Registrar::getFieldsFor($type);
 		return $this->fields;
 	}
 
@@ -589,22 +588,9 @@
 		if (!$type) {
 			return [];
 		}
-		return jvbGetSections($type, $this->object_type);
+		return Registrar::getInstance($type)->getSections()??[];
 	}
 
-    protected function getRegistry():mixed
-    {
-        switch ($this->object_type) {
-            case 'post':
-                return JVB_CONTENT[jvbNoBase(get_post_type((int)$this->object_id))]??null;
-            case 'term':
-                $term = get_term((int)$this->object_id);
-                return JVB_TAXONOMY[jvbNoBase($term->taxonomy)]??null;
-            case 'user':
-                return JVB_USER;
-        }
-        return null;
-    }
 
     /**
      * @param string $message
@@ -729,26 +715,18 @@
 
 		}
 		if (empty($fields)) {
-			$fields = ($this->content) ? jvbGetFields($this->content, $this->object_type) : $this->getFields();
+			$fields = ($this->content) ? Registrar::getFieldsFor($this->content) : $this->getFields();
 		}
 
 		if ($sections !== false && empty($sections)) {
 
-			$sections = ($this->content) ? jvbGetSections($this->content, $this->object_type) : $this->getSections();
+			$sections = ($this->content) ? Registrar::getInstance($this->content)->getSections() : $this->getSections();
 		}
 
 		if (!empty($sections)){
 			$tabs = [];
-			foreach ($sections as $slug => $title) {
-				$tabs[$slug] = [
-					'title'	=> $title,
-					'content' => '',
-					'description' => jvbSectionDescription($slug)??'',
-				];
-				$icon = jvbSectionIcon($slug);
-				if ($icon !== '') {
-					$tabs[$slug]['icon'] = $icon;
-				}
+			foreach ($sections as $config) {
+				$tabs[$config['slug']] = $config;
 			}
 		} else {
 			$tabs = false;
diff --git a/inc/meta/MetaTypeManager.php b/inc/meta/MetaTypeManager.php
index 4ea0aeb..69a2fd9 100644
--- a/inc/meta/MetaTypeManager.php
+++ b/inc/meta/MetaTypeManager.php
@@ -50,6 +50,11 @@
             'sanitize' => 'sanitize_email',
 			'default'	=> '',
         ],
+        'phone' => [
+            'type' => 'string',
+            'sanitize' => 'sanitizeTelephone',
+			'default'	=> '',
+        ],
         'url' => [
             'type' => 'string',
             'sanitize' => 'esc_url_raw',
@@ -149,4 +154,8 @@
     {
         static::$type_map[$type] = $config;
     }
+	public static function getTypes():array
+	{
+		return static::$type_map;
+	}
 }
diff --git a/inc/meta/Sanitizer.php b/inc/meta/Sanitizer.php
index 14789c2..e6cad1c 100644
--- a/inc/meta/Sanitizer.php
+++ b/inc/meta/Sanitizer.php
@@ -311,4 +311,22 @@
 			}
 			return 0.0;
 	}
+
+	public static function sanitizePhone(string|int $value, array $config = []):string
+	{
+		$digits = preg_replace('/\D/', '', (string) $value);
+
+		$length = strlen($digits);
+
+		if ($length < 10 || $length > 13) { // 13 = 3-digit country code + 10
+			return '';
+		}
+
+		$countryCode = $length > 10 ? substr($digits, 0, $length - 10) : null;
+		$number = substr($digits, -10);
+
+		$formatted = preg_replace('/(\d{3})(\d{3})(\d{4})/', '$1-$2-$3', $number);
+
+		return $countryCode ? '+' . $countryCode . '-' . $formatted : $formatted;
+	}
 }
diff --git a/inc/registrar/Fields.php b/inc/registrar/Fields.php
new file mode 100644
index 0000000..47f9f55
--- /dev/null
+++ b/inc/registrar/Fields.php
@@ -0,0 +1,173 @@
+<?php
+namespace JVBase\registrar;
+
+use JVBase\registrar\fields\Field;
+use JVBase\registrar\fields\GroupedField;
+use JVBase\registrar\fields\OptionsField;
+use JVBase\registrar\fields\TaxonomyField;
+use JVBase\registrar\fields\Upload;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Fields {
+	protected array $fields;
+	protected Registrar $registrar;
+
+	public function __construct(?string $type = null, ?Registrar $registrar = null) {
+		switch ($type) {
+			case 'post':
+				$this->addPostFields();
+				break;
+			case 'term':
+				$this->addTermFields();
+				break;
+			case 'user':
+				$this->addUserFields();
+				break;
+		}
+		$this->registrar = $registrar;
+	}
+
+	public function addField(string $name, array $config):self {
+		$this->fields[$name] = match ($config['type']) {
+			'upload', 'image', 'gallery' 	=> new Upload($name, $config),
+			'checkbox', 'radio', 'select', 'set' => new OptionsField($name, $config),
+			'repeater', 'group', 'tagList' => new GroupedField($name, $config),
+			'selector', 'taxonomy', 'user', 'post' => new TaxonomyField($name, $config),
+			default => new Field($name, $config),
+		};
+
+		return $this;
+	}
+
+	public function addPostFields():void
+	{
+		$fields = [
+			'post_thumbnail'	=> [
+				'type'	=> 'upload',
+				'multiple'=> false,
+				'label'	=> 'Main Image',
+			],
+			'post_date' => [
+				'type'	=> 'datetime',
+				'label'	=> 'Date',
+			],
+			'post_content' => [
+				'type'	=> 'textarea',
+				'quill'	=> true,
+				'label'	=> 'Content'
+			],
+			'post_title'	=> [
+				'type'		=> 'text',
+				'label'		=> 'Title',
+				'required'	=> true,
+			],
+			'post_excerpt'	=> [
+				'type'	=> 'textarea',
+				'label'	=> 'TLDR',
+				'maxLength'	=> 158,
+			],
+			'post_status'	=> [
+				'type'		=> 'radio',
+				'label'		=> 'Status',
+				'default'	=> 'draft',
+				'options'	=> [
+					'all' => [
+						'icon' => 'infinity',
+						'label' => 'Everything',
+					],
+					'publish' => [
+						'icon' => 'eye',
+						'label' => 'Live',
+					],
+					'draft' => [
+						'icon' => 'eye-closed',
+						'label' => 'Hidden',
+					],
+					'trash' => [
+						'label' => 'Scrapped',
+						'icon' => 'trash',
+					],
+					'delete'	=> [
+						'label'	=> 'Permanently Delete',
+						'icon'	=> 'trash'
+					]
+				]
+			]
+		];
+
+		foreach ($fields as $name => $config) {
+			$this->addField($name, $config);
+		}
+	}
+
+	public function addTermFields():void
+	{
+		$fields = [
+			'name'	=> [
+				'type'		=> 'text',
+				'label'		=> 'Title',
+				'required'	=> true,
+			],
+			'description'	=> [
+				'type'		=> 'textarea',
+				'quill'		=> true,
+				'label'		=> 'Description',
+			]
+		];
+		if ($this->registrar->registrar->hierarchical){
+			$fields['parent'] = [
+				'type'			=> 'taxonomy',
+				'taxonomy_type'	=> 'reference',
+				'autocomplete'	=> true,
+				'label'			=> 'Term Parent'
+			];
+		}
+		foreach ($fields as $name => $config) {
+			$this->addField($name, $config);
+		}
+	}
+
+	public function addUserFields():void
+	{
+		$fields = [
+			'first_name'	=> [
+				'type'	=> 'text',
+				'label'	=> 'First Name',
+			],
+			'last_name'	=> [
+				'type'	=> 'text',
+				'label'	=> 'Last Name',
+			],
+			'display_name'	=> [
+				'type'	=> 'text',
+				'label'	=> 'Display Name',
+			],
+			'website'	=> [
+				'type'	=> 'url',
+				'label'	=> 'Website',
+			],
+			'description'	=> [
+				'type'	=> 'textarea',
+				'quill'	=> true,
+				'label'	=> 'Description',
+			]
+		];
+		foreach ($fields as $name => $config) {
+			$this->addField($name, $config);
+		}
+	}
+
+	public function  modifyField(string $name, string $property, mixed $value):void
+	{
+		$field = $this->fields[$name];
+		$field->$property = $value;
+	}
+
+	public function getFields():array
+	{
+		return $this->fields;
+	}
+}
diff --git a/inc/registrar/Labels.php b/inc/registrar/Labels.php
new file mode 100644
index 0000000..b3d9bbc
--- /dev/null
+++ b/inc/registrar/Labels.php
@@ -0,0 +1 @@
+<?php
diff --git a/inc/registrar/Posts.php b/inc/registrar/Posts.php
new file mode 100644
index 0000000..bb63d90
--- /dev/null
+++ b/inc/registrar/Posts.php
@@ -0,0 +1,340 @@
+<?php
+namespace JVBase\registrar;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+final class Posts {
+	public string $postType;
+	public string $singular;
+	public string $plural;
+	public array $labels;
+	/**
+	 * Whether a post type is intended for use publicly either via the admin interface or by front-end users.
+	 * @var bool
+	 */
+	public bool $public = true;
+	/**
+	 * A short descriptive summary of what the post type is.
+	 * @var string
+	 */
+	public string $description;
+	/**
+	 * Whether the post type is hierarchical (e.g. page)
+	 * @var bool
+	 */
+	public bool $hierarchical = false;
+	/**
+	 * Whether to exclude posts with this post type from front end search results.
+	 * Default is the opposite value of $public
+	 * @var bool
+	 */
+	public bool $exclude_from_search;
+	/**
+	 * Whether queries can be performed on the front end for the post type as part of parse_request().
+	 * Endpoints would include:
+	 		* ?post_type={post_type_key}
+	 		* ?{post_type_key}={single_post_slug}
+	 		* ?{post_type_query_var}={single_post_slug}
+	 * If not set, the default is inherited from $public
+	 * @var bool
+	 */
+	public bool $publicly_queryable;
+	/**
+	 * Whether to generate and allow a UI for managing this post type in the admin.
+	 * Default is value of $public.
+	 * @var bool
+	 */
+	public bool $show_ui;
+	/**
+	 * Where to show the post type in the admin menu.
+	 * To work, $show_ui must be true.
+	 * If true, the post type is shown in its own top level menu.
+	 * If false, no menu is shown.
+	 * If a string of an existing top level menu ('tools.php' or 'edit.php?post_type=page', for example), the post type will be placed as a sub-menu of that.
+	 * Default is value of $show_ui.
+	 * @var bool
+	 */
+	public bool|string $show_in_menu;
+	/**
+	 * Makes this post type available for selection in navigation menus
+	 * Default is value of $public.
+	 * @var bool
+	 */
+	public bool $show_in_nav_menus;
+	/**
+	 * Makes this post type available via the admin bar.
+	 * Default is value of $show_in_menu.
+	 * @var bool
+	 */
+	public bool $show_in_admin_bar;
+	/**
+	 * Whether to include the post type in the REST API.
+	 * Set this to true for the post type to be available in the block editor.
+	 * @var bool
+	 */
+	public bool $show_in_rest;
+	/**
+	 * To change the base URL of REST API route. Default is $post_type.
+	 * @var string
+	 */
+	public string $rest_base;
+	/**
+	 * To change the namespace URL of REST API route. Default is wp/v2.
+	 * @var string
+	 */
+	public string $rest_namespace;
+	/**
+	 * REST API controller class name. Default is ‘WP_REST_Posts_Controller‘.
+	 * @var string
+	 */
+	public string $rest_controller_class;
+	/**
+	 * REST API controller class name. Default is ‘WP_REST_Autosaves_Controller‘.
+	 * @var string
+	 */
+	public string $autosave_rest_controller_class;
+	/**
+	 * REST API controller class name. Default is ‘WP_REST_Revisions_Controller‘.
+	 * @var string
+	 */
+	public string $revisions_rest_controller_class;
+	/**
+	 * A flag to direct the REST API controllers for autosave / revisions should be registered before/after the post type controller.
+	 * @var bool
+	 */
+	public bool $late_route_registration;
+	/**
+	 * The position in the menu order the post type should appear.
+	 * To work, $show_in_menu must be true. Default null (at the bottom).
+	 * @var int
+	 */
+	public int $menu_position;
+	/**
+	 * The URL to the icon to be used for this menu. Pass a base64-encoded SVG using a data URI, which will be colored to match the color scheme — this should begin with 'data:image/svg+xml;base64,'. Pass the name of a Dashicons helper class to use a font icon, e.g.
+	 * 'dashicons-chart-pie'. Pass 'none' to leave div.wp-menu-image empty so an icon can be added via CSS. Defaults to use the posts icon.
+	 * @var string
+	 */
+	public string $menu_icon;
+	/**
+	 * The string to use to build the read, edit, and delete capabilities.
+	 * May be passed as an array to allow for alternative plurals when using this argument as a base to construct the capabilities, e.g.
+	 * array('story', 'stories'). Default 'post'.
+	 * @var string|array
+	 */
+	public string|array $capability_type;
+	/**
+	 * Array of capabilities for this post type. $capability_type is used as a base to construct capabilities by default.
+	 * See get_post_type_capabilities() .
+	 * @var array
+	 */
+	public array $capabilities;
+	/**
+	 * Whether to use the internal default meta capability handling.
+	 * Default false.
+	 * @var bool
+	 */
+	public bool $map_meta_cap = false;
+	/**
+	 * Core feature(s) the post type supports. Serves as an alias for calling add_post_type_support() directly. Core features include 'title', 'editor', 'comments', 'revisions', 'trackbacks', 'author', 'excerpt', 'page-attributes', 'thumbnail', 'custom-fields', and 'post-formats'.
+	 * Additionally, the 'revisions' feature dictates whether the post type will store revisions, the 'autosave' feature dictates whether the post type will be autosaved, and the 'comments' feature dictates whether the comments count will show on the edit screen. For backward compatibility reasons, adding 'editor' support implies 'autosave' support too. A feature can also be specified as an array of arguments to provide additional information about supporting that feature.
+	 * Example: array( 'my_feature', array( 'field' => 'value' ) ).
+	 * If false, no features will be added.
+	 * Default is an array containing 'title' and 'editor'.
+	 * @var false|array|string[]
+	 */
+	public false|array $supports = ['title', 'author', 'thumbnail', 'editor', 'revisions', 'custom-fields', 'excerpt', 'content'];
+	/**
+	 * Provide a callback function that sets up the meta boxes for the edit form.
+	 * Do remove_meta_box() and add_meta_box() calls in the callback. Default null.
+	 * @var mixed|null
+	 */
+	public mixed $register_meta_box_cb = null;
+	/**
+	 * An array of taxonomy identifiers that will be registered for the post type. Taxonomies can be registered later with register_taxonomy() or register_taxonomy_for_object_type() .
+	 * @var array
+	 */
+	public array $taxonomies;
+	/**
+	 * Whether there should be post type archives, or if a string, the archive slug to use.
+	 * Will generate the proper rewrite rules if $rewrite is enabled. Default false.
+	 * @var bool|string
+	 */
+	public bool|string $has_archive = true;
+	/**
+	 * Triggers the handling of rewrites for this post type. To prevent rewrite, set to false.
+	 * Defaults to true, using $post_type as slug. To specify rewrite rules, an array can be passed with any of these keys:
+	 * slug {string} - Customize the permastruct slug. Defaults to $post_type key.
+	 * with_front {bool} - Whether the permastruct should be prepended with WP_Rewrite::$front.
+	 		* Default true.
+	 * feeds {bool} - Whether the feed permastruct should be built for this post type. Default is value of $has_archive.
+	 * pages {bool} - Whether the permastruct should provide for pagination. Default true.
+	 * ep_mask {int} -  Endpoint mask to assign. If not specified and permalink_epmask is set, inherits from $permalink_epmask. If not specified and permalink_epmask is not set, defaults to EP_PERMALINK.
+	 * @var bool|array
+	 */
+	public bool|array $rewrite;
+	/**
+	 * Sets the query_var key for this post type.
+	 * Defaults to $post_type key.
+	 * If false, a post type cannot be loaded at ?{query_var}={post_slug}.
+	 * If specified as a string, the query ?{query_var_string}={post_slug} will be valid.
+	 * @var string|bool
+	 */
+	public string|bool $query_var;
+	/**
+	 * Whether to allow this post type to be exported. Default true.
+	 * @var bool
+	 */
+	public bool $can_export = true;
+	/**
+	 * Whether to delete posts of this type when deleting a user.
+	 * If true, posts of this type belonging to the user will be moved to Trash when the user is deleted.
+	 * If false, posts of this type belonging to the user will *not* be trashed or deleted.
+	 * If not set (the default), posts are trashed if post type supports the 'author' feature. Otherwise posts are not trashed or deleted.
+	 * Default null.
+	 * @var bool
+	 */
+	public bool $delete_with_user;
+	/**
+	 * Array of blocks to use as the default initial state for an editor session. Each item should be an array containing block name and optional attributes.
+	 * @var array
+	 */
+	public array $template;
+	/**
+	 * Whether the block template should be locked if $template is set.
+	 * If set to 'all', the user is unable to insert new blocks, move existing blocks and delete blocks.
+	 * If set to 'insert', the user is able to move existing blocks but is unable to insert new blocks and delete blocks.
+	 * Default false.
+	 * @var string|false
+	 */
+	public string|false $template_lock;
+
+	public function __construct(string $postType, string $singular, string $plural)
+	{
+		$this->postType = jvbCheckBase($postType);
+		$this->labels = $this->buildLabels($singular, $plural);
+	}
+
+	public function register():void
+	{
+		$args = array_filter(get_object_vars($this));
+//		error_log('Got Object Vars: '.print_r($args, true));
+//		error_log('Filtered: '.print_r(array_filter($args), true));
+//		$args = [
+//			'labels' 		=> $this->labels,
+//			'public'		=> $this->public,
+//			'hierarchical' 	=> $this->hierarchical,
+//			'has_archive'	=> $this->has_archive,
+//			'can_export'	=> $this->can_export,
+//		];
+//		if (isset($this->exclude_from_search)) {
+//			$args['exclude_from_search'] = $this->exclude_from_search;
+//		}
+//		if (isset($this->publicly_queryable)) {
+//			$args['publicly_queryable'] = $this->publicly_queryable;
+//		}
+//		if (isset($this->show_ui)) {
+//			$args['show_ui'] = $this->show_ui;
+//		}
+//		if (isset($this->show_in_menu)) {
+//			$args['show_in_menu'] = $this->show_in_menu;
+//		}
+//		if (isset($this->show_in_nav_menus)) {
+//			$args['show_in_nav_menus'] = $this->show_in_nav_menus;
+//		}
+//		if (isset($this->show_in_admin_bar)) {
+//			$args['show_in_admin_bar'] = $this->show_in_admin_bar;
+//		}
+//		if (isset($this->show_in_rest)) {
+//			$args['show_in_rest'] = $this->show_in_rest;
+//		}
+//		if (isset($this->rest_base)) {
+//			$args['rest_base'] = $this->rest_base;
+//		}
+//		if (isset($this->rest_namespace)) {
+//			$args['rest_namespace'] = $this->rest_namespace;
+//		}
+//		if (isset($this->rest_controller_class)) {
+//			$args['rest_controller_class'] = $this->rest_controller_class;
+//		}
+//		if (isset($this->autosave_rest_controller_class)) {
+//			$args['autosave_rest_controller_class'] = $this->autosave_rest_controller_class;
+//		}
+//		if (isset($this->revisions_rest_controller_class)) {
+//			$args['revisions_rest_controller_class'] = $this->revisions_rest_controller_class;
+//		}
+//		if (isset($this->late_route_registration)) {
+//			$args['late_route_registration'] = $this->late_route_registration;
+//		}
+//		if (isset($this->menu_position)) {
+//			$args['menu_position'] = $this->menu_position;
+//		}
+//		if (isset($this->menu_icon)) {
+//			$args['menu_icon'] = $this->menu_icon;
+//		}
+//		if (isset($this->capability_type)) {
+//			$args['capability_type'] = $this->capability_type;
+//		}
+//		if (isset($this->capabilities)) {
+//			$args['capabilities'] = $this->capabilities;
+//		}
+//		if (isset($this->map_meta_cap)) {
+//			$args['map_meta_cap'] = $this->map_meta_cap;
+//		}
+//		if (isset($this->supports)) {
+//			if ($this->supports) {
+//				$allowed = ['title','editor', 'comments', 'revisions','trackbacks','author','excerpt','page-attributes','thumbnail','custom-fields','post-formats'];
+//				$this->supports = array_intersect($allowed, $this->supports);
+//			}
+//			$args['supports'] = $this->supports;
+//		}
+//		if (isset($this->register_meta_box_cb)) {
+//			$args['register_meta_box_cb'] = $this->register_meta_box_cb;
+//		}
+//		if (isset($this->taxonomies)) {
+//			$args['taxonomies'] = $this->taxonomies;
+//		}
+//		if (isset($this->rewrite)) {
+//			$args['rewrite'] = $this->rewrite;
+//		}
+//		if (isset($this->query_var)) {
+//			$args['query_var'] = $this->query_var;
+//		}
+//		if (isset($this->delete_with_user)) {
+//			$args['delete_with_user'] = $this->delete_with_user;
+//		}
+//		if (isset($this->template)) {
+//			$args['template'] = $this->template;
+//		}
+//		if (isset($this->template_lock)) {
+//			$args['template_lock'] = $this->template_lock;
+//		}
+		unset ($args['postType']);
+
+//		error_log('Registering PostType '.$this->postType.', with args: '.print_r($args, true));
+		$registered = register_post_type($this->postType, $args);
+		if (is_wp_error($registered)) {
+			JVB()->error()->log('JVBase\registrar\Posts', 'Could not register post type', $registered->get_error_messages());
+		}
+	}
+
+	private function buildLabels(string $singular, string $plural): array
+	{
+		return [
+			'name'               => $plural,
+			'singular_name'      => $singular,
+			'menu_name'          => $plural,
+			'name_admin_bar'     => $singular,
+			'add_new'            => "Add New",
+			'add_new_item'       => "Add New {$singular}",
+			'new_item'           => "New {$singular}",
+			'edit_item'          => "Edit {$singular}",
+			'view_item'          => "View {$singular}",
+			'all_items'          => "All {$plural}",
+			'search_items'       => "Search {$plural}",
+			'parent_item_colon'  => "Parent {$plural}:",
+			'not_found'          => "No {$plural} found.",
+			'not_found_in_trash' => "No {$plural} found in Trash.",
+		];
+	}
+}
diff --git a/inc/registrar/Registrar.php b/inc/registrar/Registrar.php
new file mode 100644
index 0000000..ad174ca
--- /dev/null
+++ b/inc/registrar/Registrar.php
@@ -0,0 +1,642 @@
+<?php
+namespace JVBase\registrar;
+
+use JVBase\managers\CRUD;
+use JVBase\managers\IconsManager;
+use JVBase\registrar\config\Breadcrumbs;
+use JVBase\registrar\config\Dashboard;
+use JVBase\registrar\config\Directory;
+use JVBase\registrar\config\Feed;
+use JVBase\registrar\config\Integration;
+use JVBase\registrar\config\Section;
+use JVBase\registrar\config\SEO;
+use JVBase\registrar\helpers\AddIntegrationFields;
+use JVBase\registrar\helpers\MakeCalendarType;
+use JVBase\registrar\helpers\MakeTrackChanges;
+use JVBase\registrar\helpers\MakeVerification;
+use JVBase\utility\Features;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Registrar {
+	protected string $slug;
+	protected string $type;
+	protected string $singular;
+	protected string $plural;
+	protected string $description ='';
+	protected Fields $fields;
+	protected array $sections = [];
+	//For the Terms/Post Registrar
+	protected array $args= [];
+	protected array $sectionOrder = [];
+	public Posts|Terms|false $registrar = false;
+	protected ?string $icon = null;
+
+	protected ?string $upload_title = null;
+
+	protected static array $allFlags = [
+		//Shared Flags
+		'favouritable', 'karma', 'show_feed', 'show_directory', 'approve_new', 'has_responses', 'invitable',
+		//Post Flags
+		'hide_single', 'redirect_to_author', 'is_calendar', 'single_image', 'is_timeline', 'is_gallery',
+		//Taxonomy Flags
+		'is_content', 'is_ownable', 'verify_entry', 'track_changes', 'associate_user_content',
+		//User Flags
+		'has_dashboard', 'can_register', 'can_create', 'keep_stats', 'can_favourite', 'member_verified', 'profile_link', 'manage_others'
+	];
+	/**********************************************************************************************
+	SHARED FLAGS
+	 **********************************************************************************************/
+	/**
+	 * @var bool Whether this can be favourited
+	 */
+	protected bool $favouritable = false;
+	/**
+	 * @var bool Whether to setup karma for this content
+	 */
+	protected bool $karma = false;
+	/**
+	 * @var bool Whether this should be available in the feed block
+	 */
+	protected bool $show_feed = true;
+	/**
+	 * @var bool Whether this should get a directory page
+	 */
+	protected bool $show_directory = false;
+	/**
+	 * @var bool Whether these should be approved by admin/verified users before being public
+	 */
+	protected bool $approve_new;
+	/**
+	 * @var bool Whether this item can have responses
+	 */
+	protected bool $has_responses;
+	/**
+	 * @var bool For Terms: Whether members in this taxonomy can invite other members to join
+	 * For User Roles: Whether verified members can invite people to join the site
+	 */
+	protected bool $invitable;
+	/**********************************************************************************************
+	POST FLAGS
+	 **********************************************************************************************/
+	/**
+	 * @var bool Whether single items of this content should be hidden
+	 */
+	protected bool $hide_single = false;
+
+	/**
+	 * @var bool Whether single items should just go to the author's page
+	 */
+	protected bool $redirect_to_author;
+	/**
+	 * @var bool Whether to make this a calendar type (example: events)
+	 */
+	protected bool $is_calendar;
+	/**
+	 * @var bool Whether this is a before/after post type
+	 */
+	protected bool $is_timeline = false;
+	/**
+	 * @var bool Whether the uploader can group images prior to upload
+	 */
+	protected bool $single_image;
+	/**********************************************************************************************
+	TAXONOMY FLAGS
+	 ********************************************************************************************/
+	/**
+	 * @var bool For taxonomy types only. Treats the taxonomy as a content (ie: tattoo shops))
+	 */
+	protected bool $is_content;
+	/**
+	 * @var bool Whether this taxonomy can be owned/managed by specific people only
+	 */
+	protected bool $is_ownable;
+	/**
+	 * @var bool Whether users/content need to request the owner for admission
+	 */
+	protected bool $verify_entry;
+	/**
+	 * @var bool Whether we should track post movements from term to term (ie. artists in tattoo shops)
+	 */
+	protected bool $track_changes;
+
+	/**
+	 * @var bool Whether any content by members in this taxonomy should show up in this taxonomy
+	 */
+	protected bool $associate_user_content;
+
+	/*************************************************************************************
+	USER FLAGS
+	*************************************************************************************/
+	/**
+	 * @var bool Whether this user role has access to the dashboard
+	 */
+	protected bool $has_dashboard;
+	/**
+	 * @var bool Whether this user role can register
+	 */
+	protected bool $can_register;
+	/**
+	 * @var bool Whether to track basic statistics for this role
+	 */
+	protected bool $keep_stats;
+	/**
+	 * @var bool Whether this role can favourite
+	 */
+	protected bool $can_favourite;
+	/**
+	 * @var bool Whether this role should be verified before they can publish anything
+	 */
+	protected bool $member_verified;
+	/**
+	 * @var bool Whether to generate a profile for this user role
+	 */
+	protected bool $profile_link;
+	/**
+	 * @var array|string
+	 */
+	protected array|string $can_create = [];
+	/**
+	 * @var array slugs of other user roles this role can manage
+	 */
+	protected array $manage_others = [];
+
+	/** Configs **/
+	protected Breadcrumbs $breadcrumbs;
+	protected Dashboard $dashboard;
+	protected Directory|false $directory;
+	protected Feed|false $feed;
+//	protected Management $management;
+//	protected Responses $responses;
+	protected ?SEO $seo = null;
+
+	/** Helpers **/
+	protected MakeCalendarType|false $calendar = false;
+	protected array $integrationConfigs = [];
+	protected array $integrationFields = [];
+	protected MakeTrackChanges $trackChanges;
+	protected MakeVerification $verification;
+
+	private static array $instances = [];
+
+	private function __construct(string $slug, string $singular, string $plural, string $type) {
+		$this->slug = $slug;
+		$this->type = $type;
+		$this->singular = $singular;
+		$this->plural = $plural;
+
+//		$this->init();
+
+//		$this->initClasses();
+		$this->setFields();
+
+		add_action('init', [$this, 'register'], 0);
+		add_filter('jvbDashboardPage', [$this, 'renderDashPage'], 10, 3);
+	}
+
+	protected function initRegistrar():void {
+		$this->registrar = match ($this->type) {
+			'post' => new Posts($this->slug, $this->singular, $this->plural),
+			'term' => new Terms($this->slug, $this->singular, $this->plural),
+			default => false,
+		};
+	}
+
+	protected function initClasses():void {
+		$this->breadcrumbs 	= new Breadcrumbs($this->slug);
+		$this->dashboard 	= new Dashboard($this->slug);
+		$this->directory 	= new Directory($this->singular);
+		$this->feed 		= new Feed($this->slug);
+//		$this->management 	= new Management($this->slug);
+//		$this->responses 	= new Responses($this->slug);
+		$this->seo 			= new SEO($this->slug);
+//		$this->trackChanges = new TrackChanges($this->slug);
+//		$this->Verification = new Verification($this->slug);
+	}
+
+	/**
+	 * Instantiates for post types
+	 * @param string $slug
+	 * @return self
+	 */
+	public static function forPost(string $slug, string $singular, string $plural):self
+	{
+		if (!isset(self::$instances[$slug])) {
+			self::$instances[$slug] = new self($slug, $singular, $plural,'post');
+		}
+		return self::$instances[$slug];
+	}
+
+	/**
+	 * Instantiates for term types
+	 * @param string $slug
+	 * @return self
+	 */
+	public static function forTerm(string $slug, string $singular, string $plural):self
+	{
+		if (!isset(self::$instances[$slug])) {
+			self::$instances[$slug] = new self($slug, $singular, $plural,'term');
+		}
+		return self::$instances[$slug];
+	}
+
+	/**
+	 * Instantiates for user types
+	 * @param string $slug
+	 * @return self
+	 */
+	public static function forUser(string $slug, string $singular, string $plural):self
+	{
+		if (!isset(self::$instances[$slug])) {
+			self::$instances[$slug] = new self($slug, $singular, $plural, 'user');
+		}
+		return self::$instances[$slug];
+	}
+
+	/**
+	 * Adds the properties for register_post_type or register_taxonomy
+	 * @param array $args
+	 * @return $this
+	 */
+	public function make(array $args):self
+	{
+		$this->initRegistrar();
+		if (!$this->registrar) {
+			return $this;
+		}
+		$this->args = $args;
+		foreach ($args as $property => $value) {
+			$this->registrar->$property = $value;
+		}
+		if (isset($this->icon) && !str_contains($this->icon, 'dashicons')){
+			$this->registrar->menu_icon = IconsManager::for()->getCSSIcon($this->icon);
+		}
+		return $this;
+	}
+
+	public function fields():Fields
+	{
+		return $this->fields;
+	}
+
+	public function setFields():void
+	{
+		$this->fields = new Fields($this->type, $this);
+	}
+	public function getFields():array
+	{
+		return array_map(function ($field) {
+			return $field->getConfig();
+		},
+			$this->fields->getFields()
+		);
+	}
+	public function setIcon(string $icon):self
+	{
+		$this->icon = $icon;
+		return $this;
+	}
+	public function getIcon(?string $default = null):string
+	{
+		if (!$default) {
+			$default = jvbDefaultIcon();
+		}
+
+		return $this->icon ?: $default;
+	}
+	public function getSingular():string
+	{
+		return $this->singular;
+	}
+	public function getPlural():string
+	{
+		return $this->plural;
+	}
+	public function getDescription():string
+	{
+		return $this->description;
+	}
+	public function setDescription(string $description):self
+	{
+		$this->description = $description;
+		return $this;
+	}
+
+	public function getType():string
+	{
+		return $this->type;
+	}
+	public function setIntegration(string $integration):self
+	{
+		if (!Features::forSite()->has($integration)){
+			error_log('Integration not available for '.$this->slug.': '.$integration);
+			return $this;
+		}
+		$this->integrationConfigs[$integration] = new Integration($integration, $this);
+		$this->integrationFields[$integration] = new AddIntegrationFields($integration, $this);
+		return $this;
+	}
+	public function getIntegrationFields(string $integration):AddIntegrationFields|false
+	{
+
+		if (!Features::forSite()->has($integration)){
+			error_log('Integration not available for '.$this->slug.': '.$integration);
+			return false;
+		}
+		if (!in_array($integration, $this->integrationFields)) {
+			error_log('No integration fields intitialized for '.$integration);
+			return false;
+		}
+		return $this->integrationFields[$integration];
+	}
+	public function getIntegrations():array
+	{
+		return $this->integrationConfigs;
+	}
+	public function hasIntegration(string $integration) {
+		return in_array($integration, $this->integrationConfigs);
+	}
+	public function setUploadTitle(string $title):self
+	{
+		$this->upload_title = $title;
+		return $this;
+	}
+
+	public function getUploadTitle():string {
+		return ($this->upload_title) ?: 'Upload '.$this->plural;
+	}
+
+	public function getSlug():string
+	{
+		return $this->slug;
+	}
+
+	public function setTimeline(bool $set):self
+	{
+		$this->is_timeline = $set;
+//		if ($set) {
+//			$this->timeline = new MakeTimelineType($this->slug, $this);
+//		}
+		return $this;
+	}
+	public function isTimeline():bool
+	{
+		return $this->is_timeline;
+	}
+	public function setCalendar(bool $set):self
+	{
+		$this->is_calendar = $set;
+		if ($set) {
+			$this->calendar = new MakeCalendarType($this->slug, $this);
+		}
+		return $this;
+	}
+
+	public function setAll(array $flags):self
+	{
+		$flags = array_filter($flags, function($flag) {
+			return in_array($flag, static::$allFlags);
+		});
+		foreach ($flags as $flag) {
+			$this->$flag = true;
+		}
+		return $this;
+	}
+	public function removeAll(array $flags):self
+	{
+		$flags = array_filter($flags, function($flag) {
+			return in_array($flag, static::$allFlags);
+		});
+		foreach ($flags as $flag) {
+			$this->$flag = false;
+		}
+		return $this;
+	}
+
+
+	public function hasFeature(string $feature):bool
+	{
+		if (!in_array($feature, static::$allFlags)) {
+			return false;
+		}
+		return isset($this->$feature) && $this->$feature === true;
+	}
+	public static function getFeatured(string $feature, ?string $type = null):array
+	{
+		if (!in_array($feature, static::$allFlags)) {
+			error_log('Feature requested not found: '.$feature);
+			return [];
+		}
+
+		return array_map(function($inst) { return $inst->slug; },array_filter(self::$instances, function ($inst) use ($feature, $type){
+			return isset($inst->$feature) && $inst->$feature === true && (is_null($type) || $inst->type === $type);
+		}));
+	}
+
+	public function config(string $config):mixed
+	{
+		$allowed = ['breadcrumbs','calendar','dashboard','directory','feed','management','has_responses','seo','trackchanges','verification'];
+		if (!in_array(strtolower($config), $allowed)) {
+			error_log('Invalid config requested from Registrar: '.$config);
+			return [];
+		}
+		return match(strtolower($config)) {
+			'breadcrumbs' 	=> $this->getBreadcrumbs(),
+			'dashboard'		=> $this->getDashboard(),
+			'directory'		=> $this->getDirectory(),
+			'feed'			=> $this->getFeed(),
+			'management'	=> $this->getManagement(),
+			'has_responses'	=> $this->getResponses(),
+			'seo'			=> $this->getSEO(),
+			'trackchanges'	=> $this->getTrackChanges(),
+			'verification'	=> $this->getVerification()
+		};
+	}
+		protected function getBreadcrumbs():Breadcrumbs
+		{
+			$this->breadcrumbs = new Breadcrumbs($this->slug, $this);
+			return $this->breadcrumbs;
+		}
+		protected function getCalendar():MakeCalendarType|false
+		{
+			if ($this->is_calendar){
+				$this->calendar = new MakeCalendarType($this->slug, $this);
+			} else {
+				$this->calendar = false;
+			}
+			return $this->calendar;
+		}
+
+		protected function getDashboard():Dashboard
+		{
+			$this->dashboard = new Dashboard($this->plural, $this);
+			return $this->dashboard;
+		}
+
+		protected function getDirectory():Directory|false
+		{
+			if ($this->show_directory) {
+				$this->directory = new Directory($this->singular, $this);
+			} else {
+				$this->directory = false;
+			}
+			return $this->directory;
+		}
+
+		protected function getFeed():Feed|false
+		{
+			if ($this->show_feed) {
+				$this->feed = new Feed($this->slug, $this);
+			} else {
+				$this->feed = false;
+			}
+			return $this->feed;
+		}
+
+		public function getSEO():SEO
+		{
+			if (!isset($this->seo)){
+				$this->seo = new SEO($this->slug, $this);
+			}
+			return $this->seo;
+		}
+
+	public function getSections():array
+	{
+		$allSections = array_map(function($section) {
+			return $section->getConfig;
+		}, $this->sections);
+
+		if (!empty($this->sectionOrder)) {
+			$allSections['order'] = $this->sectionOrder;
+		}
+		return $allSections;
+	}
+	public function addSection(string $title):Section
+	{
+		$section = new Section($title, $this);
+		$this->sections[] = $section;
+		return $section;
+	}
+
+	public function setSectionOrder(array $sections):self
+	{
+		$allSections = array_map(function($section) {
+			return $section->getSlug;
+		}, $this->sections);
+		$this->sectionOrder = array_intersect($allSections, $sections);
+		return $this;
+	}
+	public function getSectionOrder():array
+	{
+		return $this->sectionOrder;
+	}
+
+	public function getConfig(string $config):array
+	{
+		$allowed = ['breadcrumbs','calendar','dashboard','directory','feed','management','has_responses','seo','trackchanges','verification'];
+		if (!in_array(strtolower($config), $allowed)) {
+			error_log('Invalid config requested from Registrar: '.$config);
+			return [];
+		}
+		$config = $this->config($config);
+		return ($config) ? $config->getConfig() : [];
+	}
+
+	public function getIntegration(string $service_name):Integration|false
+	{
+		if (array_key_exists($service_name, $this->integrationConfigs)) {
+			return $this->integrationConfigs[$service_name];
+		}
+		return false;
+	}
+	public function getIntegrationConfig(string $service_name):array|false
+	{
+		$integration = $this->getIntegration($service_name);
+		if ($integration){
+			return $integration->getConfig();
+		}
+		return false;
+	}
+
+	public function register():void
+	{
+		if ($this->registrar) {
+			$this->registrar->register();
+		}
+
+	}
+	public static function getInstance(string $slug):Registrar|false
+	{
+		$slug = jvbNoBase($slug);
+		if (array_key_exists($slug, static::$instances)) {
+			return static::$instances[$slug];
+		}
+		return false;
+	}
+
+	public static function getFieldsFor(string $slug):array
+	{
+		if (!array_key_exists($slug, static::$instances)) {
+			return [];
+		}
+		$instance = static::$instances[$slug];
+		return $instance->getFields();
+	}
+
+	public static function getRegistered(?string $type = null):array
+	{
+		$instances = ($type) ? array_filter(static::$instances, function($instance) use ($type) {
+			return $instance->type === $type;
+		}) : static::$instances;
+		return array_keys($instances);
+	}
+
+	public static function getLabels():array
+	{
+		return array_map(function ($instance) {
+			return ['singular' => $instance->getSingular(),
+				'plural' => $instance->getPlural()];
+		}, static::$instances);
+	}
+
+	public function getCreatable():array
+	{
+		if ($this->type !== 'user') {
+			return [];
+		}
+		return $this->can_create;
+	}
+	public function setCreatable(string|array $creatable):self
+	{
+		$this->can_create = $creatable;
+		return $this;
+	}
+
+	public function getManageOthers():array
+	{
+		if ($this->type !== 'user'){
+			return [];
+		}
+		return $this->manage_others;
+	}
+	public function setManageOthers(array $manageable):self
+	{
+		$this->manage_others = $manageable;
+		return $this;
+	}
+
+	public function renderDashPage(string $content, string $page, string $slug):string
+	{
+		if ($slug === $this->slug) {
+			ob_start();
+			$crud = new CRUD($slug);
+			$crud->render();
+			return ob_get_clean();
+		}
+
+		return $content;
+	}
+}
diff --git a/inc/registrar/Terms.php b/inc/registrar/Terms.php
new file mode 100644
index 0000000..20890dd
--- /dev/null
+++ b/inc/registrar/Terms.php
@@ -0,0 +1,255 @@
+<?php
+namespace JVBase\registrar;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+final class Terms {
+	public string $taxonomy;
+	public string $singular;
+	public string $plural;
+	public array $labels;
+	/**
+	 * A string or array of strings of object types this taxonomy is for
+	 * @var string|array
+	 */
+	public string|array $for;
+	/**
+	 * A short descriptive summary of what the taxonomy is for.
+	 * @var string
+	 */
+	public string $description;
+	/**
+	 * Whether a taxonomy is intended for use publicly either via the admin interface or by front-end users.
+	 * The default settings of $publicly_queryable, $show_ui, and $show_in_nav_menus are inherited from $public.
+	 * @var bool
+	 */
+	public bool $public = true;
+	/**
+	 * Whether the taxonomy is publicly queryable.
+	 * If not set, the default is inherited from $public
+	 * @var bool
+	 */
+	public bool $publicly_queryable;
+	/**
+	 * Whether the taxonomy is hierarchical. Default false.
+	 * @var bool
+	 */
+	public bool $hierarchical = false;
+	/**
+	 * Whether to generate and allow a UI for managing terms in this taxonomy in the admin.
+	 * If not set, the default is inherited from $public (default true).
+	 * @var bool
+	 */
+	public bool $show_ui;
+	/**
+	 * Whether to show the taxonomy in the admin menu. If true, the taxonomy is shown as a submenu of the object type menu. If false, no menu is shown.
+	 * $show_ui must be true. If not set, default is inherited from $show_ui (default true).
+	 * @var bool
+	 */
+	public bool $show_in_menu;
+	/**
+	 * Makes this taxonomy available for selection in navigation menus.
+	 * If not set, the default is inherited from $public (default true).
+	 * @var bool
+	 */
+	public bool $show_in_nav_menus;
+	/**
+	 * Whether to include the taxonomy in the REST API. Set this to true for the taxonomy to be available in the block editor.
+	 * @var bool
+	 */
+	public bool $show_in_rest;
+	/**
+	 * To change the base url of REST API route. Default is $taxonomy.
+	 * @var string
+	 */
+	public string $rest_base;
+	/**
+	 * To change the namespace URL of REST API route. Default is wp/v2.
+	 * @var string
+	 */
+	public string $rest_namespace;
+	/**
+	 * REST API Controller class name. Default is ‘WP_REST_Terms_Controller‘.
+	 * @var string
+	 */
+	public string $rest_controller_class;
+	/**
+	 * Whether to list the taxonomy in the Tag Cloud Widget controls.
+	 * If not set, the default is inherited from $show_ui (default true).
+	 * @var bool
+	 */
+	public bool $show_tag_cloud;
+	/**
+	 * Whether to show the taxonomy in the quick/bulk edit panel.
+	 * If not set, the default is inherited from $show_ui (default true).
+	 * @var bool
+	 */
+	public bool $show_quick_edit;
+	/**
+	 * Whether to display a column for the taxonomy on its post type listing screens. Default false
+	 * @var bool
+	 */
+	public bool $show_admin_column;
+	/**
+	 * Provide a callback function for the meta box display.
+	 * If not set, post_categories_meta_box() is used for hierarchical taxonomies, and post_tags_meta_box() is used for non-hierarchical.
+	 * If false, no meta box is shown.
+	 * @var mixed
+	 */
+	public mixed $meta_box_cb;
+	/**
+	 * Callback function for sanitizing taxonomy data saved from a meta box. If no callback is defined, an appropriate one is determined based on the value of $meta_box_cb.
+	 * @var mixed
+	 */
+	public mixed $meta_box_sanitize_cb;
+	/**
+	 * Array of capabilities for this taxonomy.
+	 * manage_terms {string} 	- Default 'manage_categories'.
+	 * edit_terms {string}		- Default 'manage_categories'.
+	 * delete_terms {string}	- Default 'manage_categories'.
+	 * assign_terms {string}	- Default 'edit_posts'.
+	 * @var array
+	 */
+	public array $capabilities;
+	/**
+	 * Triggers the handling of rewrites for this taxonomy. Default true, using $taxonomy as slug. To prevent rewrite, set to false. To specify rewrite rules, an array can be passed with any of these keys:
+	 	* slug {string}		- Customize the permastruct slug. Default $taxonomy key.
+	 	* with_front {bool}	-  Should the permastruct be prepended with WP_Rewrite::$front. Default true.
+	 	* hierarchical {bool}- Either hierarchical rewrite tag or not. Default false.
+	 	* ep_mask {int}		- Assign an endpoint mask. Default EP_NONE.
+	 * @var bool|array
+	 */
+	public bool|array $rewrite;
+	/**
+	 * Sets the query var key for this taxonomy. Default $taxonomy key.
+	 * If false, a taxonomy cannot be loaded at ?{query_var}={term_slug}.
+	 * If a string, the query ?{query_var}={term_slug} will be valid.
+	 * @var bool|string
+	 */
+	public bool|string $query_var;
+	/**
+	 * Works much like a hook, in that it will be called when the count is updated.
+	 * Default _update_post_term_count() for taxonomies attached to post types, which confirms that the objects are published before counting them.
+	 * Default _update_generic_term_count() for taxonomies attached to other object types, such as users.
+	 * @var mixed
+	 */
+	public mixed $update_count_callback;
+	/**
+	 * Default term to be used for the taxonomy.
+	 	* name {string}	- Name of the default term
+	 	* slug {string} - Slug for the default term
+	 	* description {string} - Description for default term
+	 * @var string|array
+	 */
+	public string|array $default_term;
+	/**
+	 * Whether terms in this taxonomy should be sorted in the order they are provided to wp_set_object_terms().
+	 * Default null which equates to false.
+	 * @var bool
+	 */
+	public bool $sort;
+	/**
+	 * Array of arguments to automatically use inside wp_get_object_terms() for this taxonomy.
+	 * @var array
+	 */
+	public array $args;
+
+
+	public function __construct(string $taxonomy, string $singular, string $plural)
+	{
+		$this->taxonomy = $taxonomy;
+		$this->labels = $this->buildLabels($singular, $plural);
+	}
+	private function buildLabels(string $singular, string $plural): array
+	{
+		return [
+			'name' => $plural,
+			'singular_name' => $singular,
+			'search_items' => "Search {$plural}",
+			'all_items' => "All {$plural}",
+			'edit_item' => "Edit {$singular}",
+			'update_item' => "Update {$singular}",
+			'add_new_item' => "Add New {$singular}",
+			'new_item_name' => "New {$singular} Name",
+			'menu_name' => $singular
+		];
+	}
+
+	public function register():void
+	{
+		$args = [
+			'labels'		=> $this->labels,
+			'public'		=> $this->public,
+			'hierarchical'	=> $this->hierarchical,
+		];
+			if (isset($this->description)) {
+				$args['description'] = $this->description;
+			}
+			if (isset($this->publicly_queryable)) {
+				$args['publicly_queryable'] = $this->publicly_queryable;
+			}
+			if (isset($this->show_ui)) {
+				$args['show_ui'] = $this->show_ui;
+			}
+			if (isset($this->show_in_menu)) {
+				$args['show_in_menu'] = $this->show_in_menu;
+			}
+			if (isset($this->show_in_nav_menus)) {
+				$args['show_in_nav_menus'] = $this->show_in_nav_menus;
+			}
+			if (isset($this->show_in_rest)) {
+				$args['show_in_rest'] = $this->show_in_rest;
+			}
+			if (isset($this->rest_base)) {
+				$args['rest_base'] = $this->rest_base;
+			}
+			if (isset($this->rest_namespace)) {
+				$args['rest_namespace'] = $this->rest_namespace;
+			}
+			if (isset($this->rest_controller_class)) {
+				$args['rest_controller_class'] = $this->rest_controller_class;
+			}
+			if (isset($this->show_tag_cloud)) {
+				$args['show_tag_cloud'] = $this->show_tag_cloud;
+			}
+			if (isset($this->show_quick_edit)) {
+				$args['show_quick_edit'] = $this->show_quick_edit;
+			}
+			if (isset($this->show_admin_column)) {
+				$args['show_admin_column'] = $this->show_admin_column;
+			}
+			if (isset($this->meta_box_cb) && is_callable($this->meta_box_cb)) {
+				$args['meta_box_cb'] = $this->meta_box_cb;
+			}
+			if (isset($this->meta_box_sanitize_cb) && is_callable($this->meta_box_sanitize_cb)) {
+				$args['meta_box_sanitize_cb'] = $this->meta_box_sanitize_cb;
+			}
+			if (isset($this->capabilities)) {
+				$allowed = ['manage_terms', 'edit_terms', 'delete_terms', 'assign_terms'];
+				$caps = array_filter($this->capabilities, function ($cap) use ($allowed) {
+					return in_array($cap, $allowed);
+				}, ARRAY_FILTER_USE_KEY);
+				$args['capabilities'] = $caps;
+			}
+			if (isset($this->query_var)) {
+				$args['query_var'] = $this->query_var;
+			}
+			if (isset($this->update_count_callback) && is_callable($this->update_count_callback)) {
+				$args['update_count_callback'] = $this->update_count_callback;
+			}
+			if (isset($this->default_term)) {
+				$args['default_term'] = $this->default_term;
+			}
+			if (isset($this->sort)) {
+				$args['sort'] = $this->sort;
+			}
+			if (isset($this->args)) {
+				$args['args'] = $this->args;
+			}
+
+		$for = array_map(function($item) { return jvbCheckBase($item);}, $this->for);
+		register_taxonomy(jvbCheckBase($this->taxonomy), $for, $args);
+	}
+}
diff --git a/src/list/index.php b/inc/registrar/Users.php
similarity index 100%
copy from src/list/index.php
copy to inc/registrar/Users.php
diff --git a/inc/registrar/_setup.php b/inc/registrar/_setup.php
new file mode 100644
index 0000000..a6eddc2
--- /dev/null
+++ b/inc/registrar/_setup.php
@@ -0,0 +1,25 @@
+<?php
+require(JVB_DIR . '/inc/registrar/config/Config.php');
+require(JVB_DIR . '/inc/registrar/config/Breadcrumbs.php');
+require(JVB_DIR . '/inc/registrar/config/Dashboard.php');
+require(JVB_DIR . '/inc/registrar/config/Directory.php');
+require(JVB_DIR . '/inc/registrar/config/Feed.php');
+require(JVB_DIR . '/inc/registrar/config/SEO.php');
+require(JVB_DIR . '/inc/registrar/config/seo/_setup.php');
+
+require(JVB_DIR . '/inc/registrar/fields/Field.php');
+require(JVB_DIR . '/inc/registrar/fields/GroupedField.php');
+require(JVB_DIR . '/inc/registrar/fields/OptionsField.php');
+require(JVB_DIR . '/inc/registrar/fields/TaxonomyField.php');
+require(JVB_DIR . '/inc/registrar/fields/Upload.php');
+
+require(JVB_DIR . '/inc/registrar/helpers/MakeCalendarType.php');
+require(JVB_DIR . '/inc/registrar/helpers/MakeTrackChanges.php');
+require(JVB_DIR . '/inc/registrar/helpers/MakeVerification.php');
+
+require(JVB_DIR . '/inc/registrar/Fields.php');
+require(JVB_DIR . '/inc/registrar/Posts.php');
+require(JVB_DIR . '/inc/registrar/Terms.php');
+require(JVB_DIR . '/inc/registrar/Users.php');
+
+require(JVB_DIR . '/inc/registrar/Registrar.php');
diff --git a/inc/registrar/config/Breadcrumbs.php b/inc/registrar/config/Breadcrumbs.php
new file mode 100644
index 0000000..9d7b8e5
--- /dev/null
+++ b/inc/registrar/config/Breadcrumbs.php
@@ -0,0 +1,48 @@
+<?php
+namespace JVBase\registrar\config;
+
+
+use JVBase\registrar\Registrar;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+final class Breadcrumbs extends Config {
+	public string $title;
+	public null|string|array $addCrumb = null;
+
+	public function __construct(string $title) {
+		$this->title = $title;
+	}
+
+	public function setTitle(string $title): self {
+		$this->title = $title;
+		return $this;
+	}
+	public function getTitle():string
+	{
+		return $this->title;
+	}
+
+	public function setCrumb(string|array $crumb):self
+	{
+		$this->addCrumb = $crumb;
+		return $this;
+	}
+	public function getCrumb():string|array
+	{
+		return $this->addCrumb;
+	}
+
+	function getConfig(): array
+	{
+		$config = [
+			'title'	=> $this->title,
+		];
+		if (isset($this->addCrumb)) {
+			$config['addCrumb'] = $this->addCrumb;
+		}
+		return $config;
+	}
+}
diff --git a/inc/registrar/config/Config.php b/inc/registrar/config/Config.php
new file mode 100644
index 0000000..9c5d083
--- /dev/null
+++ b/inc/registrar/config/Config.php
@@ -0,0 +1,10 @@
+<?php
+namespace JVBase\registrar\config;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+abstract class Config {
+	abstract public function getConfig():array;
+}
diff --git a/inc/registrar/config/Dashboard.php b/inc/registrar/config/Dashboard.php
new file mode 100644
index 0000000..f5c68e7
--- /dev/null
+++ b/inc/registrar/config/Dashboard.php
@@ -0,0 +1,44 @@
+<?php
+namespace JVBase\registrar\config;
+
+use JVBase\registrar\Registrar;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+final class Dashboard extends Config{
+	public string $title;
+	public string $description = '';
+
+	public function __construct(string $title) {
+		$this->title = 'Your ' . $title;
+	}
+
+	public function setTitle(string $title): self {
+		$this->title = $title;
+		return $this;
+	}
+	public function getTitle():string
+	{
+		return $this->title;
+	}
+
+	public function setDescription(string $description):self
+	{
+		$this->description = $description;
+		return $this;
+	}
+	public function getDescription():string|array
+	{
+		return $this->description;
+	}
+
+	public function getConfig(): array
+	{
+		return [
+			'title'			=> $this->title,
+			'description'	=> $this->description,
+		];
+	}
+}
diff --git a/inc/registrar/config/Directory.php b/inc/registrar/config/Directory.php
new file mode 100644
index 0000000..d20dfd1
--- /dev/null
+++ b/inc/registrar/config/Directory.php
@@ -0,0 +1,73 @@
+<?php
+namespace JVBase\registrar\config;
+
+use JVBase\registrar\Registrar;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+final class Directory extends Config{
+	public string $title;
+	public array $description = [];
+	protected bool $isGrouped = false;
+	protected array $groupDescription;
+	protected array $directoryExtra = [];
+
+	public function __construct(string $title) {
+		$this->title = $title;
+	}
+
+	public function setTitle(string $title): self {
+		$this->title = $title;
+		return $this;
+	}
+	public function getTitle():string
+	{
+		return $this->title;
+	}
+
+	public function setDescription(array $description):self
+	{
+		$this->description = $description;
+		return $this;
+	}
+	public function getDescription():array
+	{
+		return $this->description;
+	}
+
+	public function setGroupDescription(array $description):self
+	{
+		$this->groupDescription = $description;
+		return $this;
+	}
+	public function getGroupDescription():array
+	{
+		return $this->groupDescription;
+	}
+
+	public function setDirectoryExtra(array $extra):self
+	{
+		$this->directoryExtra = $extra;
+		return $this;
+	}
+	public function getDirectoryExtra():array
+	{
+		return $this->directoryExtra;
+	}
+
+	public function getConfig(): array
+	{
+		$config = [
+			'title' => $this->title,
+			'description' => $this->description,
+			'isGrouped'	=> $this->isGrouped,
+			'directory_extra' => $this->directoryExtra,
+		];
+		if (isset($this->groupDescription) && !empty($this->groupDescription)) {
+			$config['groupDescription'] = $this->groupDescription;
+		}
+		return $config;
+	}
+}
diff --git a/inc/registrar/config/Feed.php b/inc/registrar/config/Feed.php
new file mode 100644
index 0000000..9e23b3c
--- /dev/null
+++ b/inc/registrar/config/Feed.php
@@ -0,0 +1,83 @@
+<?php
+namespace JVBase\registrar\config;
+
+use JVBase\registrar\Registrar;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+final class Feed extends Config {
+	protected array $single = [];
+	protected array $archive = [];
+	protected array $config = [];
+	protected string $slug;
+
+	public function __construct(string $slug) {
+		$this->slug = $slug;
+		$this->config = $this->defaultConfig();
+	}
+
+	public function defaultConfig():array
+	{
+		return [
+			'is_gallery'	=> false,
+			'icon'			=> Registrar::getInstance($this->slug)->getIcon(),
+			'content'		=> $this->slug,
+//			'taxonomies'	=> Registrar::getInstance($this->slug)->registrar->taxonomies,
+		];
+	}
+
+	protected function allowedConfig():array
+	{
+		return ['is_gallery', 'content', 'context', 'taxonomies','icon','source','custom_order','images','fields'];
+	}
+
+	public function setGallery(bool $set = true):self
+	{
+		$this->config['is_gallery'] = $set;
+		return $this;
+	}
+	public function setIcon(string $icon):self
+	{
+		$this->config['icon'] = $icon;
+		return $this;
+	}
+	public function setCustomOrder(array $order):self
+	{
+		$this->config['custom_order'] = $order;
+		return $this;
+	}
+	public function getCustomOrder():array {
+		return $this->config['custom_order']??[];
+	}
+	public function setImages(string|array $images):self
+	{
+		$images = is_string($images) ? [$images] : $images;
+		$images = array_filter($images, function($imgField) {
+			return array_key_exists($imgField, Registrar::getInstance($this->slug)->getFields());
+		});
+		if (empty($images)) {
+			$images = ['post_thumbnail'];
+		}
+		$this->config['images'] = $images;
+		return $this;
+	}
+	public function setFields(array $fields):self
+	{
+		$fields = array_filter($fields, function($field){
+			return array_key_exists($field, Registrar::getInstance($this->slug)->getFields());
+		});
+		if (empty($fields)) {
+			$fields = ['post_title', 'post_date','post_excerpt'];
+		}
+		$this->config['fields'] = $fields;
+		return $this;
+	}
+	public function getConfig():array
+	{
+		return $this->config;
+	}
+
+
+}
diff --git a/inc/registrar/config/Integration.php b/inc/registrar/config/Integration.php
new file mode 100644
index 0000000..8723e71
--- /dev/null
+++ b/inc/registrar/config/Integration.php
@@ -0,0 +1,122 @@
+<?php
+namespace JVBase\registrar\config;
+
+use JVBase\registrar\Registrar;
+use JVBase\utility\Features;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+final class Integration extends Config{
+	/**
+	 * @var string Must match as defined in the JVBase\integrations namespace
+	 */
+	protected string $service_name;
+	protected string $content_type;
+	/**
+	 * @var bool Whether to send to the integration on publish
+	 */
+	protected bool $initial;
+	/**
+	 * @var bool Whether to sync changes with the service
+	 */
+	protected bool $update;
+	protected bool $canSchedule;
+	/**
+	 * @var bool whether this is a customer role, used if this is a user-based Registrar
+	 */
+	protected bool $isCustomer;
+
+
+
+	public function __construct(string $service) {
+		if (!Features::hasIntegration($service)) {
+			error_log('Could not link with integration, as it is not enabled: '.$service);
+			return;
+		}
+		$this->service_name = $service;
+	}
+
+	public function getService_name():string
+	{
+		return $this->service_name;
+	}
+
+	/**
+	 * @param string $content must match what integration expects
+	 * @return self
+	 */
+	public function setContent_type(string $content):self
+	{
+		$allowed = JVB()->connect($this->service_name)->allowedContent();
+		if (!in_array($content, $allowed)) {
+			error_log($this->service_name.' Connection does not support this content: '.$content);
+			return $this;
+		}
+		$this->content_type = $content;
+		return $this;
+	}
+
+	public function getContentType():string
+	{
+		return $this->content_type;
+	}
+
+	public function setInitial(bool $set):self
+	{
+		$this->initial = $set;
+		return $this;
+	}
+	public function getInitial():bool|null
+	{
+		return $this->initial??null;
+	}
+
+	public function setUpdate(bool $set):self
+	{
+		$this->update = $set;
+		return $this;
+	}
+	public function getUpdate():bool|null
+	{
+		return $this->update ?? null;
+	}
+
+	public function setCustomer(bool $set):self
+	{
+		$this->isCustomer = $set;
+		return $this;
+	}
+	public function isCustomer():bool
+	{
+		return $this->isCustomer??false;
+	}
+
+
+	public function setCanSchedule(bool $set):self
+	{
+		$this->canSchedule = $set;
+		return $this;
+	}
+	public function getCanSchedule():bool|null
+	{
+		return $this->canSchedule??null;
+	}
+
+	public function getConfig(): array
+	{
+		$config = [];
+		if (isset($this->content_type)) {
+			$config['content_type'] = $this->content_type;
+		}
+		if (isset($this->initial)) {
+			$config['initial'] = $this->initial;
+		}
+		if (isset($this->update)) {
+			$config['update'] = $this->update;
+		}
+
+		return $config;
+	}
+}
diff --git a/src/list/index.php b/inc/registrar/config/Management.php
similarity index 100%
copy from src/list/index.php
copy to inc/registrar/config/Management.php
diff --git a/src/list/index.php b/inc/registrar/config/Responses.php
similarity index 100%
copy from src/list/index.php
copy to inc/registrar/config/Responses.php
diff --git a/inc/registrar/config/SEO.php b/inc/registrar/config/SEO.php
new file mode 100644
index 0000000..7617b7b
--- /dev/null
+++ b/inc/registrar/config/SEO.php
@@ -0,0 +1,67 @@
+<?php
+
+namespace JVBase\registrar\config;
+
+use JVBase\registrar\config\seo\Archive;
+use JVBase\registrar\config\seo\Meta;
+use JVBase\registrar\config\seo\Schema;
+use JVBase\registrar\Registrar;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+final class SEO extends Config
+{
+	protected string $slug;
+	protected array $config;
+	public Schema $schema;
+	public Meta $meta;
+	public Archive $archive;
+
+
+	public function __construct(string $slug)
+	{
+		$this->slug = $slug;
+		$action = ucFirst($slug);
+		$this->config = [
+			'schema' 	=> get_option(BASE.$action.'Schema', apply_filters(BASE.$action.'Schema', [])),
+			'meta'		=> get_option(BASE.$action.'Meta', apply_filters(BASE.$action.'Meta', [])),
+			'archive'	=> get_option(BASE.$action.'Archive', apply_filters(BASE.$action.'Archive', [])),
+		];
+	}
+
+	protected function initSchema():void
+	{
+		if (!array_key_exists('type', $this->config['schema'])){
+			error_log('Missing schema type');
+			return;
+		}
+		if (!class_exists($this->config['schema']['type'])) {
+			error_log('Could not find class: '.$this->config['schema']['type']);
+			return;
+		}
+		$this->schema = new Schema($this->slug, $this->config['schema']['type']);
+
+	}
+	public function schema():Schema|false
+	{
+		if (!isset($this->schema)) {
+			$this->initSchema();
+		}
+		return $this->schema??false;
+	}
+	public function meta():Meta
+	{
+		return $this->meta;
+	}
+	public function archive():Archive
+	{
+		return $this->archive;
+	}
+
+	public function getConfig():array
+	{
+		return  $this->config;
+	}
+}
diff --git a/inc/registrar/config/Section.php b/inc/registrar/config/Section.php
new file mode 100644
index 0000000..15e2def
--- /dev/null
+++ b/inc/registrar/config/Section.php
@@ -0,0 +1,111 @@
+<?php
+namespace JVBase\registrar\config;
+
+use JVBase\registrar\Registrar;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+final class Section extends Config{
+	protected string $title;
+	protected string $slug;
+	protected string $description = '';
+	protected string $icon = '';
+	protected array $fields = [];
+
+	public function __construct(string $title) {
+		$this->title = $title;
+		$this->slug = sanitize_title($title);
+	}
+
+	public function setTitle(string $title): self {
+		$this->title = $title;
+		return $this;
+	}
+	public function getTitle():string
+	{
+		return $this->title;
+	}
+
+	public function setDescription(string $description):self
+	{
+		$this->description = $description;
+		return $this;
+	}
+	public function getDescription():string|array
+	{
+		return $this->description;
+	}
+
+	public function setIcon(string $icon):self
+	{
+		$this->icon = $icon;
+		return $this;
+	}
+
+	protected function checkFields(string|array $fields):string|array
+	{
+		$allFields =  Registrar::getInstance($this->slug)->getFields();
+		if (is_array($fields)){
+			foreach ($fields as $index =>$fieldName){
+				if (!array_key_exists($fieldName, $allFields)){
+					error_log('Attempted to add '.$fieldName.' to section. No field defined.');
+					unset($fields[$index]);
+				}
+			}
+		} else {
+			if (!array_key_exists($fields, $allFields)){
+				error_log('Attempted to add '.$fields.' to section. No field defined.');
+				return '';
+			}
+		}
+		return $fields;
+	}
+
+
+
+	public function setFields(array $fields):self
+	{
+		$fields = $this->checkFields($fields);
+		$this->fields = $fields;
+		return $this;
+	}
+	public function addField(string $field):self
+	{
+		$field = $this->checkFields($field);
+		if ($field !== ''){
+			$this->fields[] = $field;
+		}
+		return $this;
+	}
+
+	public function getFields():array
+	{
+		return $this->fields;
+	}
+
+	/**
+	 * If the user has defined a 'section' key for this section for specific fields, they can autogenerate the section with this
+	 * @return $this
+	 */
+	public function generateFields():self
+	{
+		$this->fields = array_keys(array_filter(Registrar::getInstance($this->slug)->getFields(), function($field){
+			return array_key_exists('section', $field) && $field['section'] === $this->slug;
+		}));
+		return $this;
+
+	}
+
+	public function getConfig(): array
+	{
+		return [
+			'slug'			=> $this->slug,
+			'title'			=> $this->title,
+			'description'	=> $this->description,
+			'icon'			=> $this->icon,
+			'fields'		=> $this->fields,
+		];
+	}
+}
diff --git a/src/list/index.php b/inc/registrar/config/TrackChanges.php
similarity index 100%
copy from src/list/index.php
copy to inc/registrar/config/TrackChanges.php
diff --git a/src/list/index.php b/inc/registrar/config/Verification.php
similarity index 100%
copy from src/list/index.php
copy to inc/registrar/config/Verification.php
diff --git a/inc/registrar/config/seo/Archive.php b/inc/registrar/config/seo/Archive.php
new file mode 100644
index 0000000..1a07dcc
--- /dev/null
+++ b/inc/registrar/config/seo/Archive.php
@@ -0,0 +1,10 @@
+<?php
+namespace JVBase\registrar\config\seo;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Archive {
+
+}
diff --git a/inc/registrar/config/seo/Helpers.php b/inc/registrar/config/seo/Helpers.php
new file mode 100644
index 0000000..ce4ae53
--- /dev/null
+++ b/inc/registrar/config/seo/Helpers.php
@@ -0,0 +1,29 @@
+<?php
+
+use JVBase\meta\Meta;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+function jvbTSFDoIt(string $slug, ?array $args):bool
+{
+	$tsf = tsf();
+	return (null === $args)
+		? $tsf->query()->is_singular()
+		&& BASE.$slug === $tsf->query()->get_current_post_type()
+		: 'single' === The_SEO_Framework\get_query_type_from_args( $args )
+		&& BASE.$slug === $tsf->query()->get_post_type_real_id( $args['id'] );
+}
+
+function jvbTSFGetID(?array $args):int
+{
+	return (null === $args)
+		? tsf()->query()->get_the_real_id()
+		: $args['id'];
+}
+
+function jvbParseMeta(int $ID, string $parse, Meta $meta):string
+{
+
+}
diff --git a/inc/registrar/config/seo/Meta.php b/inc/registrar/config/seo/Meta.php
new file mode 100644
index 0000000..4ab4021
--- /dev/null
+++ b/inc/registrar/config/seo/Meta.php
@@ -0,0 +1,87 @@
+<?php
+namespace JVBase\registrar\config\seo;
+
+use JVBase\registrar\Registrar;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Meta {
+	protected string $slug;
+
+	protected string $title;
+	protected string $description;
+	protected int $image;
+	protected int $twitterImage;
+
+
+	public function __construct(string $slug) {
+		$this->slug = $slug;
+		add_action('init', [$this, 'init']);
+	}
+
+	public function init():void {
+		if (!function_exists('tsf')){
+			return;
+		}
+		if ($this->hasTitle()){
+			add_filter('the_seo_framework_title_from_generation', [$this, 'filterTitle'], 10, 2);
+		}
+		if ($this->hasDescription()){
+			add_filter('the_seo_framework_generated_description', [$this, 'filterDescription'], 10, 3);
+		}
+		if ($this->hasImage()){
+			add_filter('the_seo_framework_image_generation_params', [$this, 'filterImage'], 10, 3);
+		}
+	}
+
+	public function hasTitle():bool
+	{
+		return !empty($this->title);
+	}
+	public function hasDescription():bool
+	{
+		return !empty($this->description);
+	}
+	public function hasImage():bool
+	{
+		return !empty($this->image);
+	}
+	public function hasTwitterImage():bool
+	{
+		return !empty($this->twitterImage);
+	}
+
+	public function filterTitle(string $title, ?array $args): string
+	{
+		if (jvbTSFDoIt($this->slug, $args)){
+			return $title;
+		}
+		$ID = jvbTSFGetID($args);
+
+		$meta = new \JVBase\meta\Meta($ID, Registrar::getInstance($this->slug)->getType());
+		return Resolver::resolve($this->title, $meta);
+	}
+
+	public function filterDescription(string $description, ?array $args): string
+	{
+		if (jvbTSFDoIt($this->slug, $args)){
+			return $description;
+		}
+		$ID = jvbTSFGetID($args);
+
+		$meta = new \JVBase\meta\Meta($ID, Registrar::getInstance($this->slug)->getType());
+		return Resolver::resolve($this->title, $meta);
+	}
+	public function filterImage(string $image, ?array $args): string
+	{
+		if (jvbTSFDoIt($this->slug, $args)){
+			return $image;
+		}
+		$ID = jvbTSFGetID($args);
+
+		$meta = new \JVBase\meta\Meta($ID, Registrar::getInstance($this->slug)->getType());
+		return Resolver::resolve($this->title, $meta);
+	}
+}
diff --git a/inc/registrar/config/seo/Resolver.php b/inc/registrar/config/seo/Resolver.php
new file mode 100644
index 0000000..e754477
--- /dev/null
+++ b/inc/registrar/config/seo/Resolver.php
@@ -0,0 +1,179 @@
+<?php
+namespace JVBase\registrar\config\seo;
+
+use JVBase\managers\Cache;
+use JVBase\managers\SEO\render\Thing\CreativeWork\MediaObject\ImageObject;
+use JVBase\managers\SEO\render\Thing\Intangible\Quantity\Distance;
+use JVBase\meta\Meta;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Resolver {
+	protected static Meta $meta;
+
+	public static function resolve(string $template, Meta $meta): string
+	{
+		self::$meta = $meta;
+		return preg_replace_callback(
+			'/\{\{([^}]+)}}/',
+			fn($matches) => self::resolveVariable($matches[1], $meta),
+			$template
+		);
+	}
+	protected static function resolveVariable(string $variable, Meta $meta): string
+	{
+		$variable = trim($variable);
+
+		if (str_contains($variable, '.')) {
+			return self::resolveRelation($variable, $meta);
+		}
+		if ($variable === 'post_permalink') {
+			return get_the_permalink($meta->id());
+		}
+
+		$config = $meta->config($variable);
+		if (!$config) {
+			error_log('[SEO]Meta Resolver. Could not find meta configuration for variable: '.$variable);
+			return '';
+		}
+		return match($config['type']) {
+			'upload', 'image', 'gallery' => self::resolveImage($variable, $meta),
+			'selector', 'taxonomy', 'user', 'post' => self::resolveObject($variable, $meta),
+			default => $meta->get($variable),
+		};
+	}
+
+	protected static function resolveRelation(string $path, Meta $meta): string
+	{
+		$parts = explode('.', $path);
+		$relation = array_shift($parts);
+		$field = implode('.', $parts);
+
+		//We need to:
+			// 1) Get the id of the item we're fetching (meta value of the $relation)
+		$ID = $meta->get($relation);
+		if (!$ID || $ID === '') {
+			return '';
+		}
+			// 2) Create new Meta for that relation
+		$config = $meta->config($relation);
+		if (!$config) {
+			return '';
+		}
+		$type = false;
+		if ($config['type'] === 'taxonomy' || array_key_exists('taxonomy', $config)) {
+			$type = 'taxonomy';
+		} elseif ($config['type'] === 'user'){
+			$type = 'user';
+		} elseif ($config['type'] === 'post'){
+			$type = 'post';
+		}
+		if (!$type) {
+			error_log('[SEO]Meta Resolver. Could not find type for relation: '.$relation.': '.$field);
+			return '';
+		}
+		$newMeta = new Meta($ID, $type);
+			// 3) Pass to resolver
+		return self::resolve($field, $newMeta);
+	}
+
+	protected static function resolveImage(string $variable, Meta $meta, bool $returnID = false): string
+	{
+		$imgID = $meta->get($variable);
+		if (!$imgID || $imgID === '') {
+			return '';
+		}
+		if ($returnID) {
+			return $imgID;
+		}
+		$image = wp_get_attachment_image_src($imgID,'full');
+		if (!$image) {
+			return '';
+		}
+		return $image[0];
+	}
+	protected static function resolveObject(string $variable, Meta $meta): string
+	{
+		//Hmmm... this should already be handled by dot notation.
+		return '';
+	}
+
+	/**
+	 * SCHEMA RESOLVING
+	 * We need to map the values to what schema.org expects.
+	 * Most are defined in the JVBase\managers\schema\render namespace.
+	 */
+	public static function resolveForSchema(string $property, string $value, mixed $schema, Meta $meta):mixed
+	{
+		$check = 'resolve'.ucfirst($property).'Property';
+		if (method_exists(self::class, $check)) {
+			return self::$check($property, $value, $meta);
+		}
+
+		$checkType = self::checkPropertyType($property, $value, $schema, $meta);
+		if ($checkType !== false) {
+			return $checkType;
+		}
+
+		error_log('[SEO]Resolver - No method found for '.$property.' with value: '.print_r($value, true).'. Defaulting to base Resolver');
+
+		return self::resolve($value, $meta);
+	}
+
+		public static function checkPropertyType(string $property, mixed $value, mixed $schema, Meta $meta):mixed
+		{
+			return match($property) {
+				'logo', 'image', 'photo', 'primaryImageOfPage', 'thumbnail', 'associatedMedia' => self::resolveImageProperty($property, $value, $schema, $meta),
+				default => false
+			};
+
+		}
+
+		public static function resolveImageProperty(string $property, mixed $value, mixed $schema, Meta $meta):?ImageObject
+		{
+			$value = str_replace('{{', '', str_replace('}}', '', $value));
+			$imgID = $meta->get($value);
+			error_log('Got image id: '.print_r($imgID, true));
+			if (!$imgID || $imgID === '') {
+				return null;
+			}
+			$img = wp_get_attachment_image_src($imgID,'full');
+			if (!$img) {
+				return null;
+			}
+			Cache::for('imageSchemaObject')->flush();
+			return Cache::for('imageSchemaObject')->connect('post')->remember(
+				$imgID,
+				function () use ($imgID, $img) {
+					$imageObject = new ImageObject();
+					$imageObject->setContentUrl($img[0]);
+					$width = new Distance();
+					$width->setName($img[1].' px');
+					$imageObject->setWidth($width);
+					$height = new Distance();
+					$height->setName($img[2].' px');
+					$imageObject->setHeight($height);
+
+					$image_path = get_attached_file($imgID);
+					if ($image_path && file_exists($image_path)) {
+						$bytes = filesize($image_path);
+						$imageObject->setContentSize($bytes);
+
+						$fileType = wp_check_filetype($image_path);
+						if ($fileType['ext']) {
+							$imageObject->setEncodingFormat($fileType['ext']);
+						}
+					}
+
+					$caption = wp_get_attachment_caption($imgID)??get_post($imgID)->post_content??false;
+					if ($caption && $caption!== '') {
+						$imageObject->setCaption($caption);
+					}
+					return $imageObject;
+				}
+			);
+
+		}
+}
diff --git a/inc/registrar/config/seo/Schema.php b/inc/registrar/config/seo/Schema.php
new file mode 100644
index 0000000..91f03cb
--- /dev/null
+++ b/inc/registrar/config/seo/Schema.php
@@ -0,0 +1,297 @@
+<?php
+namespace JVBase\registrar\config\seo;
+
+use JVBase\managers\Cache;
+use JVBase\managers\SEO\render;
+use JVBase\meta\Meta;
+use JVBase\registrar\Registrar;
+use WP_Query;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Schema {
+	//One of the defined classes in the JVBase\managers\SEO\render namespace;
+	protected mixed $schema;
+	protected string $slug;
+	protected Meta $meta;
+	protected Cache $cache;
+	protected Cache $referenceCache;
+	protected Cache $archiveCache;
+
+	protected array $properties = [];
+	protected array $referenceProperties = [];
+	protected array $defaultReference = [
+		'name'	=> '{{post_title}}',
+		'url'	=> '{{post_permalink}}',
+		'description' => '{{post_excerpt}}',
+	];
+	protected array $defaultSchema = [
+		'type'	=> 'JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork',
+		'title'	=> '{{post_title}}',
+		'url'	=> '{{post_permalink}}',
+		'description' => '{{post_excerpt}}',
+	];
+
+	protected array $defaultArchive = [];
+
+	public function __construct(string $slug, string $type)
+	{
+		if (!class_exists($type)) {
+			error_log('Could not find schema class: '.$type);
+			return;
+		}
+		$this->schema = new $type();
+		$this->slug = $slug;
+		$registrar = Registrar::getInstance($this->slug);
+		$this->cache = Cache::for('schema');
+		$this->referenceCache = Cache::for('schemaReference');
+		$this->archiveCache = Cache::for('schemaArchive');
+		if ($registrar) {
+			$this->cache->connect($registrar->getType());
+			$this->referenceCache->connect($registrar->getType());
+			$this->archiveCache->connect($registrar->getType());
+		}
+		$this->initFilters();
+		$this->registerHooks();
+	}
+
+	public function initFilters():void
+	{
+		$referenceProperties = apply_filters(BASE.ucFirst($this->slug).'ReferenceProperties', $this->defaultReference);
+		foreach ($referenceProperties as $property => $value) {
+			$this->defineReference($property, $value);
+		}
+
+		$registrar = Registrar::getInstance($this->slug);
+		$this->defaultArchive = [
+			'type'	=> 'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\CollectionPage',
+			'name'	=> $registrar->getPlural(),
+			'description' => $registrar->getDescription()
+		];
+	}
+	public function registerHooks(): void
+	{
+		add_action('wp_head', [$this, 'outputSchema'], 1);
+		add_filter('the_seo_framework_schema_graph_data', [$this, 'filterTSFSchema'], 10, 2);
+		$this->maybeExcludeSingles();
+	}
+		protected function maybeExcludeSingles(): void
+		{
+			$exclude = $this->cache->remember(
+				'excludeSingles',
+				function () {
+					$exclude = [];
+					$registrar = Registrar::getInstance($this->slug);
+					if ($registrar && $registrar->hasFeature('hide_single')) {
+						$exclude = $this->excludeSingle();
+					}
+					if ($registrar && $registrar->hasFeature('is_timeline')) {
+						$exclude = array_merge($exclude, $this->excludeTimeline());
+					}
+					return $exclude;
+				}
+			);
+
+			if (!empty($exclude)) {
+				add_filter('the_seo_framework_sitemap_exclude_ids', $exclude);
+			}
+		}
+		protected function excludeSingle():array
+		{
+			return get_posts([
+				'post_type'		=> jvbCheckBase($this->slug),
+				'posts_per_page'=> -1,
+				'fields'		=> 'ids',
+				'post_status'	=> 'publish',
+			]);
+		}
+		protected function excludeTimeline():array
+		{
+			return get_posts([
+				'post_type'		=> jvbCheckBase($this->slug),
+				'posts_per_page'=> -1,
+				'fields'		=> 'ids',
+				'post_status'	=> 'publish',
+				'post_parent__not_in'	=> [0], // Only get posts with a parent
+			]);
+		}
+	public function filterTSFSchema(array $graph, ?array $args):array
+	{
+		if (jvbTSFDoIt($this->slug, $args)){
+			return [];
+		}
+		return $graph;
+	}
+
+	public function outputSchema():void
+	{
+		if (is_singular()) {
+			$this->outputSingularSchema();
+		} elseif (is_post_type_archive(jvbCheckBase($this->slug) || is_tax(jvbCheckBase($this->slug)))) {
+			$this->outputArchiveSchema();
+		}
+	}
+		public function outputSingularSchema():array
+		{
+			$ID = get_the_ID();
+			$this->cache->flush();
+			return $this->cache->remember(
+				$ID,
+				function () use ($ID) {
+					$meta = Meta::forPost($ID);
+					$config = $this->getConfig();
+
+					$class = new $config['type']();
+					unset($config['type']);
+					foreach ($config as $property => $value){
+						$value = Resolver::resolveForSchema($property, $value, $config, $meta);
+						$method = 'set'.ucfirst($property);
+						$class->$method($value);
+					}
+
+					$class->setAuthor(JVB()->seo()->getCreator(true));
+					$schema = $class->outputSchema();
+					error_log('Generated archive schema: '.print_r($schema, true));
+					return $schema;
+				}
+			);
+
+		}
+
+		public function outputArchiveSchema():array
+		{
+			$this->archiveCache->flush();
+			return $this->archiveCache->remember(
+				$this->slug,
+				function() {
+					$action = BASE.ucfirst($this->slug).'Archive';
+					$config = get_option($action, apply_filters($action, $this->defaultArchive));
+
+					if (!class_exists($config['type'])) {
+						error_log('No class found for archive schema output: '.$config['type']);
+						return [];
+					}
+
+					$class = new $config['type'];
+					unset($config['type']);
+					foreach ($config as $property=>$value) {
+						$method = 'set'.ucfirst($property);
+						$class->$method($value);
+					}
+					$class->setIsPartOf(get_home_url().'/#website');
+					$itemList = new render\Thing\Intangible\ItemList\ItemList();
+					$items = new WP_Query([
+						'post_type'		=> jvbCheckBase($this->slug),
+						'posts_per_page'=> 25,
+						'post_status'	=> 'publish',
+						'fields'		=> 'ids'
+					]);
+					$pos = 1;
+					$itemListItems = [];
+					foreach ($items->posts as $ID) {
+						$item = $this->outputReferenceSchema($ID, false);
+						$listItem = new render\Thing\Intangible\ListItem();
+						$listItem->setPosition($pos);
+						$listItem->setItem($item);
+						$itemListItems[] = $listItem;
+						$pos++;
+					}
+					wp_reset_postdata();
+					$itemList->setItemListElement($itemListItems);
+					$class->setMainEntity($itemList);
+
+					$schema = $class->outputSchema();
+					error_log('Generated archive schema: '.print_r($schema, true));
+					return $schema;
+				}
+			);
+		}
+
+	public function outputReferenceSchema(int $ID, bool $outputSchema = true):mixed
+	{
+		$this->referenceCache->flush();
+		$cached = $this->referenceCache->remember(
+			$ID,
+			function () use ($ID) {
+				$meta = Meta::forPost($ID);
+				$config = $this->getConfig();
+				$class = new $config['type']();
+				$class->setId(get_the_permalink($ID).'/#'.$class->getTypeName());
+				foreach ($this->referenceProperties as $property => $value){
+					$value = Resolver::resolveForSchema($property, $value, $this->schema, $meta);
+					$method = 'set'.ucfirst($property);
+					$class->$method($value);
+				}
+
+				$schema = $class->outputSchema();
+				error_log('Generated archive schema: '.print_r($schema, true));
+				return $class;
+			}
+		);
+
+		return $outputSchema ? $cached->outputSchema() : $cached;
+	}
+
+	public function getConfig(string $type = 'schema'):array
+	{
+		if (!in_array(strtolower($type), ['schema', 'meta', 'archive', 'reference'])) {
+			error_log('[SEO]Schema::getConfig Invalid type: '.$type);
+			return [];
+		}
+		$action = BASE.ucfirst($this->slug).ucfirst($type);
+		$default = 'default'.ucfirst($type);
+		return get_option($action, apply_filters($action, $this->$default));
+	}
+
+	public function define(string $property, string $value):void
+	{
+		$class = $this->getConfig('schema')['type'];
+		if (!class_exists($class)) {
+			error_log('[SEO]Schema::defineReference Class not found: '.$class);
+			return;
+		}
+		if ($property === 'type') {
+			$this->properties[$property] = $value;
+			return;
+		}
+		if (!property_exists($class, $property)){
+			error_log('Attempted to add non-existent property '.$property.' with value: '.print_r($value, true));
+			return;
+		}
+		$this->properties[$property] = $value;
+	}
+	public function defineReference(string $property, string $value):void
+	{
+		$class = $this->getConfig('schema')['type'];
+		if (!class_exists($class)) {
+			error_log('[SEO]Schema::defineReference Class not found: '.$class);
+			return;
+		}
+		if ($property === 'type') {
+			$this->referenceProperties[$property] = $value;
+			return;
+		}
+		$class = new $class();
+		if (!property_exists($class, $property)){
+			error_log('Attempted to add non-existent property '.$property.' with value: '.print_r($value, true));
+			return;
+		}
+		$this->referenceProperties[$property] = $value;
+	}
+
+	public function setAllProperties(array $properties):void
+	{
+		foreach ($properties as $property => $value){
+			$this->define($property, $value);
+		}
+	}
+	public function setAllReferenceProperties(array $properties):void
+	{
+
+		foreach ($properties as $property => $value){
+			$this->defineReference($property, $value);
+		}
+	}
+}
diff --git a/inc/registrar/config/seo/_setup.php b/inc/registrar/config/seo/_setup.php
new file mode 100644
index 0000000..e3f1986
--- /dev/null
+++ b/inc/registrar/config/seo/_setup.php
@@ -0,0 +1,7 @@
+<?php
+
+require(JVB_DIR . '/inc/registrar/config/seo/Helpers.php');
+require(JVB_DIR . '/inc/registrar/config/seo/Resolver.php');
+require(JVB_DIR . '/inc/registrar/config/seo/Archive.php');
+require(JVB_DIR . '/inc/registrar/config/seo/Meta.php');
+require(JVB_DIR . '/inc/registrar/config/seo/Schema.php');
diff --git a/src/list/index.php b/inc/registrar/fields/Calendar.php
similarity index 100%
copy from src/list/index.php
copy to inc/registrar/fields/Calendar.php
diff --git a/inc/registrar/fields/Field.php b/inc/registrar/fields/Field.php
new file mode 100644
index 0000000..6bbe401
--- /dev/null
+++ b/inc/registrar/fields/Field.php
@@ -0,0 +1,143 @@
+<?php
+namespace JVBase\registrar\fields;
+
+use JVBase\meta\MetaTypeManager;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Field {
+	protected string $name;				// field name. Will be prefixed with BASE
+	protected string $type;				// field type
+	protected string $label;			// define its label
+	protected mixed $default;			// default value
+	protected string $description;
+	protected string $hint;
+	protected bool $required = false;	// whether it is required
+	protected bool $hidden = false;		// whether to show in the editor
+	protected bool $quickEdit = true;	// whether to show in quick edit table
+	protected bool $quill;				// whether to use quill
+	protected int $maxLength;			// of characters
+	/**
+	 * @var ?bool For timeline post types. Indicates whether all posts get this field, or just the parent
+	 */
+	protected ?bool $for_all = null;
+	/**
+	 * @var ?string Rather than manually defining sections, one can define which section a field belongs to here
+	 */
+	protected ?string $section = null;
+
+	public function __construct(string $name, array $config) {
+		$this->name = $name;
+		$required = ['type', 'label'];
+		foreach ($required as $r) {
+			if (!array_key_exists($r, $config)) {
+				error_log('[JVBase\registrar\Field] Missing required '.$r.' for field');
+				return;
+			}
+		}
+		foreach ($config as $key => $value) {
+			if (property_exists($this, $key)) {
+				$method = 'set' . ucfirst($key);
+				$this->$method($value);
+			} else {
+				error_log('Instance: '.print_r($this, true));
+				error_log('[JVBase\registrar\Field] Invalid key for '.$name.': '.$key);
+			}
+		}
+	}
+
+	public function setDescription(string $description):void
+	{
+		$this->description = $description;
+	}
+	public function getDescription():string
+	{
+		return $this->description;
+	}
+	public function setHint(string $hint):void
+	{
+		$this->hint = $hint;
+	}
+	public function getHint():string
+	{
+		return $this->hint;
+	}
+
+	protected function setType(string $type):void{
+		$allowed = array_keys(MetaTypeManager::getTypes());
+		if (!in_array($type, $allowed)) {
+			error_log('[JVBase\registrar\Field] Invalid type attempted '.$type);
+			return;
+		}
+		$this->type = $type;
+	}
+
+	protected function setLabel(string $label):void{
+		$this->label = $label;
+	}
+	protected function setRequired(bool $required):void{
+		$this->required = $required;
+	}
+	protected function setHidden(bool $hidden):void{
+		$this->hidden = $hidden;
+	}
+	protected function setQuickEdit(bool $quickEdit):void{
+		$this->quickEdit = $quickEdit;
+	}
+	protected function setDefault(mixed $default):void
+	{
+		$this->default = $default;
+	}
+
+	protected function setQuill(bool $quill):void
+	{
+		$this->quill = $quill;
+	}
+
+	public function setForAll(bool $set):void
+	{
+		$this->for_all = $set;
+	}
+	public function getForAll():?bool
+	{
+		return $this->for_all??null;
+	}
+	public function setSection(string $section):void
+	{
+		$this->section = $section;
+	}
+	public function getSection():?string
+	{
+		return $this->section??null;
+	}
+
+	protected function setMaxLength(int $maxLength):void
+	{
+		$this->maxLength = $maxLength;
+	}
+	public function getConfig():array{
+		$config = get_object_vars($this);
+
+		$config = array_map(function ($item) {
+			if (is_a($item, Field::class)) {
+				return $item->getConfig();
+			} else if (is_array($item)) {
+				$temp = [];
+				foreach ($item as $v) {
+					if (is_a($v, Field::class)) {
+						$temp[] = $v->getConfig();
+					} else {
+						$temp[] = $v;
+					}
+				}
+				return $temp;
+			} else {
+				return $item;
+			}
+		}, $config);
+
+		return $config;
+	}
+}
diff --git a/inc/registrar/fields/GroupedField.php b/inc/registrar/fields/GroupedField.php
new file mode 100644
index 0000000..99a8211
--- /dev/null
+++ b/inc/registrar/fields/GroupedField.php
@@ -0,0 +1,24 @@
+<?php
+namespace JVBase\registrar\fields;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class GroupedField extends Field {
+	protected array $fields;
+
+	public function setFields(array $fields):void
+	{
+		foreach ($fields as $name => $config) {
+			$this->fields[$name] = match ($config['type']) {
+				'upload', 'image', 'gallery' 	=> new Upload($name, $config),
+				'checkbox', 'radio', 'select', 'set' => new OptionsField($name, $config),
+				'repeater', 'group', 'tagList' => new GroupedField($name, $config),
+				'selector', 'taxonomy', 'user', 'post' => new TaxonomyField($name, $config),
+				default => new Field($name, $config),
+			};
+		}
+	}
+}
diff --git a/inc/registrar/fields/OptionsField.php b/inc/registrar/fields/OptionsField.php
new file mode 100644
index 0000000..ee9d641
--- /dev/null
+++ b/inc/registrar/fields/OptionsField.php
@@ -0,0 +1,15 @@
+<?php
+namespace JVBase\registrar\fields;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class OptionsField extends Field {
+	protected array $options;
+
+	public function setOptions(array $options):void
+	{
+		$this->options = $options;
+	}
+}
diff --git a/inc/registrar/fields/TaxonomyField.php b/inc/registrar/fields/TaxonomyField.php
new file mode 100644
index 0000000..da92231
--- /dev/null
+++ b/inc/registrar/fields/TaxonomyField.php
@@ -0,0 +1,19 @@
+<?php
+namespace JVBase\registrar\fields;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class TaxonomyField extends Field {
+	protected bool $autocomplete;
+	protected string $taxonomy;
+	protected string $taxonomy_type;
+
+	public function setTaxonomy(string $taxonomy) {
+		$this->taxonomy = $taxonomy;
+	}
+	public function setTaxonomy_type(string $taxonomy_type) {
+		$this->taxonomy_type = $taxonomy_type;
+	}
+}
diff --git a/inc/registrar/fields/Upload.php b/inc/registrar/fields/Upload.php
new file mode 100644
index 0000000..8f04148
--- /dev/null
+++ b/inc/registrar/fields/Upload.php
@@ -0,0 +1,47 @@
+<?php
+namespace JVBase\registrar\fields;
+
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Upload extends Field {
+	protected bool $multiple = false;
+	protected string $subtype;
+
+	protected int $maxUploads;
+
+	protected function setMultiple(bool $set):void
+	{
+		$this->multiple = $set;
+	}
+	protected function getMultiple():bool
+	{
+		return $this->multiple;
+	}
+
+	protected function setMaxUploads(int $maxUploads):void
+	{
+		$max = 20;
+		$this->maxUploads = min($maxUploads, $max);
+	}
+	protected function getMaxUploads():int
+	{
+		return $this->maxUploads;
+	}
+
+	protected function setSubtype(string $subtype):void
+	{
+		$allowed = ['document', 'video', 'image', 'all'];
+		if (!in_array($subtype, $allowed)) {
+			error_log('Invalid subtype for '.$this->name.' image field: '.$subtype);
+			return;
+		}
+		$this->subtype = $subtype;
+	}
+	protected function getSubtype():string
+	{
+		return $this->subtype;
+	}
+}
diff --git a/inc/registrar/helpers/AddIntegrationFields.php b/inc/registrar/helpers/AddIntegrationFields.php
new file mode 100644
index 0000000..fde540d
--- /dev/null
+++ b/inc/registrar/helpers/AddIntegrationFields.php
@@ -0,0 +1,135 @@
+<?php
+namespace JVBase\registrar\helpers;
+
+use JVBase\registrar\config\Integration;
+use JVBase\registrar\Registrar;
+use JVBase\utility\Features;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class AddIntegrationFields {
+	protected string $service_name;
+	protected Registrar $registrar;
+	protected Integration $config;
+	protected array $allowed;
+
+	public function __construct(string $service_name, ?Registrar $registrar = null) {
+		$this->initAllowed();
+		if (!in_array($service_name, $this->allowed)) {
+			return;
+		}
+
+		$this->service_name = $service_name;
+		if ($registrar) {
+			$this->registrar = $registrar;
+		}
+
+		$this->config = $registrar->getIntegration($service_name);
+
+	}
+	protected function initAllowed():void
+	{
+		$allowed = [];
+		if (Features::hasIntegration('gmb')) {
+			$allowed['gmb'] = 'Google My Business';
+		}
+		if (Features::hasIntegration('facebook')) {
+			$allowed['facebook'] = 'Facebook';
+		}
+		if (Features::hasIntegration('square')) {
+			$allowed['square'] = 'Square';
+		}
+		if (Features::hasIntegration('instagram')) {
+			$allowed['instagram'] = 'Instagram';
+		}
+		if (Features::hasIntegration('bluesky')) {
+			$allowed['bluesky'] = 'BlueSky';
+		}
+		if (Features::hasIntegration('helcim')) {
+			$allowed['helcim'] = 'Helcim';
+		}
+		$this->allowed = $allowed;
+	}
+
+	public function addIntegrationFields():void
+	{
+		$fields = $this->getIntegrationFields();
+		foreach ($fields as $fieldName => $fieldConfig) {
+			$this->registrar->fields()->addField($fieldName, $fieldConfig);
+		}
+	}
+
+	public function getIntegrationFields():array
+	{
+		$fields = [
+			'share_to_'.$this->service_name => [
+				'type'	=> 'true_false',
+				'label'	=> 'Share To '.$this->allowed[$this->service_name],
+				'section'	=> 'sync',
+			]
+		];
+		if ($this->config->getUpdate()){
+			$fields['_keep_synced_'.$this->service_name] = [
+				'type'	=> 'true_false',
+				'label'	=> 'Keep Synced with '.$this->allowed[$this->service_name],
+				'section' => 'sync',
+				'condition' => [
+					'field'	=> 'share_to_'.$this->service_name,
+					'value'	=> 1,
+					'operator'	=> '=='
+				]
+			];
+
+			$fields["_{$this->service_name}_item_id"] = [
+				'type'		=> 'text',
+				'label'		=> $this->allowed[$this->service_name].' ID',
+				'section'	=> 'sync',
+				'hidden'	=> true,
+			];
+			$fields["_{$this->service_name}_shared_at"] = [
+				'type'		=> 'datetime',
+				'label'		=> $this->allowed[$this->service_name].' Shared at:',
+				'section'	=> 'sync',
+				'hidden'	=> true,
+			];
+			$fields["_{$this->service_name}_last_sync"] = [
+				'type'		=> 'datetime',
+				'label'		=> $this->allowed[$this->service_name].' Last Sync',
+				'section'	=> 'sync',
+				'hidden'	=> true
+			];
+			$fields["_{$this->service_name}_sync_status"] = [
+				'type'	=> 'select',
+				'label'	=> 'Sync Status',
+				'options' => [
+					'synced' => 'Synced',
+					'pending' => 'Pending',
+					'failed' => 'Failed',
+					'unpublished' => 'Unpublished',
+					'scheduled' => 'Scheduled'
+				],
+				'section' => 'sync',
+				'hidden' => true,
+			];
+		}
+
+		if ($this->config->getCanSchedule()) {
+			$fields['schedule_'.$this->service_name] = [
+				'type'	=> 'datetime',
+				'label'	=> 'Schedule for later?',
+				'condition' => [
+					'field' => 'share_to_'.$this->service_name,
+					'operator' => '==',
+					'value' => 1
+				]
+			];
+		}
+		$additional = JVB()->connect($this->service_name)->getAdditionalFields($this->config->getContentType());
+		if (!empty($additional)) {
+			$fields = array_merge($fields, $additional);
+		}
+		return $fields;
+	}
+}
diff --git a/inc/registrar/helpers/MakeCalendarType.php b/inc/registrar/helpers/MakeCalendarType.php
new file mode 100644
index 0000000..5ca3bf7
--- /dev/null
+++ b/inc/registrar/helpers/MakeCalendarType.php
@@ -0,0 +1,334 @@
+<?php
+namespace JVBase\registrar\helpers;
+
+use JVBase\meta\Meta;
+use JVBase\registrar\Registrar;
+use WP_Post;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class MakeCalendarType {
+	protected string $slug;
+	protected string $postType;
+	protected Registrar $registrar;
+	public function __construct(string $slug, Registrar $registrar) {
+		$this->slug = $slug;
+		$this->postType = jvbCheckBase($slug);
+		$this->registrar = $registrar;
+
+		$this->addCalendarFields();
+		add_filter('post_type_link', [$this, 'handleCalendarLinks'], 15, 2);
+		add_filter('post_type_archive_link', [$this, 'handlePostTypeArchiveLinks'], 15, 2);
+		add_filter('query_vars', [$this, 'addQueryVars']);
+		add_action('init', [$this, 'addCalendarRewrites']);
+	}
+
+	protected function addCalendarFields():void
+	{
+
+		$fields = [
+			'location' => [
+				'type' => 'location',
+				'label' => 'Event Location',
+			],
+			'location_notes' => [
+				'type'	=> 'textarea',
+				'quill'	=> true,
+				'label' => 'Location Notes',
+			],
+			'date_start' => [
+				'type'      => 'date',
+				'label'     => __('Date', 'jvb'),
+			],
+			'time_start' => [
+				'type'      => 'time',
+				'label'     => __('Time Start', 'jvb'),
+			],
+			'time_end' => [
+				'type'      => 'time',
+				'label'     => __('Time End', 'jvb'),
+			],
+			'make_multiple' => [
+				'type'      => 'true_false',
+				'label'     => 'Make Multi-Day Event',
+				'default'   => false,
+			],
+			'date_end' => [
+				'type'      => 'date',
+				'label'     => __('Date End', 'jvb'),
+				'condition' => [
+					'field' => 'make_multiple',
+					'operator' => '==',
+					'value' => 1
+				]
+			],
+			'past' => [
+				'type'      => 'true_false',
+				'label'     => __('Past Event', 'jvb'),
+				'hidden'    => true,
+			],
+			'year' => [
+				'type'      => 'number',
+				'label'     => __('Year', 'jvb'),
+				'hidden'    => true,
+			],
+			'month' => [
+				'type'      => 'number',
+				'label'     => __('Month', 'jvb'),
+				'hidden'    => true,
+			],
+			'day' => [
+				'type'      => 'number',
+				'label'     => __('Day', 'jvb'),
+				'hidden'    => true,
+			],
+			'schedule' => [
+				'type' => 'repeater',
+				'label' => 'Daily Schedule',
+				'description' => 'Set specific times for multi-day events',
+				'condition'	=> [
+					'field'	=> 'make_multiple',
+					'operator' => '==',
+					'value'	=> 1,
+				],
+				'fields' => [
+					'date' => [
+						'type' => 'date',
+						'label' => 'Date'
+					],
+					'time_start' => [
+						'type' => 'time',
+						'label' => 'Start Time'
+					],
+					'time_end' => [
+						'type' => 'time',
+						'label' => 'End Time'
+					],
+					'notes' => [
+						'type' => 'textarea',
+						'label' => 'Notes for this day'
+					]
+				]
+			],
+			'max_participants' => [
+				'type'      => 'number',
+				'label'     => __('Maximum Participants', 'jvb'),
+				'bulkEdit'=> true,
+			],
+			'is_free' => [
+				'type' => 'true_false',
+				'label' => 'Free Event?',
+				'default' => 1,
+				'bulkEdit'=> true,
+			],
+			'cost' => [
+				'type' => 'number',
+				'label' => 'Cost to Attend',
+				'description' => 'Leave blank if free',
+				'condition' => [
+					'field' => 'is_free',
+					'operator' => '!=',
+					'value' => 1
+				],
+				'bulkEdit'=> true,
+			],
+			'ticket_url' => [
+				'type' => 'url',
+				'label' => 'Link to Purchase Ticket',
+				'condition' => [
+					'field' => 'is_free',
+					'operator' => '!=',
+					'value' => 1
+				]
+			],
+			'external_url' => [
+				'type' => 'url',
+				'label' => 'Link to More Info'
+			],
+			'drop_in_allowed' => [
+				'type' => 'true_false',
+				'label' => 'Drop-ins Welcome?',
+				'default' => true,
+				'bulkEdit'=> true,
+			],
+			'recurrence' => [
+				'type' => 'group',
+				'label' => 'Event Recurrence',
+				'condition'	=> [
+					'field'	=> 'status',
+					'operator' => '==',
+					'value'	=> 'repeat',
+				],
+				'fields' => [
+					'recurrence_type' => [
+						'type' => 'select',
+						'label' => 'Repeats',
+						'options' => [
+							'daily' => 'Daily',
+							'weekly' => 'Weekly',
+							'monthly' => 'Monthly',
+							'custom' => 'Custom'
+						]
+					],
+					'recurrence_interval' => [
+						'type' => 'number',
+						'label' => 'Every',
+						'description' => 'Repeat every X days/weeks/months',
+						'default' => 1,
+					],
+					'recurrence_days' => [
+						'type' => 'set',
+						'label' => 'On These Days',
+						'options' => [
+							'monday' => 'Monday',
+							'tuesday' => 'Tuesday',
+							'wednesday' => 'Wednesday',
+							'thursday' => 'Thursday',
+							'friday' => 'Friday',
+							'saturday' => 'Saturday',
+							'sunday' => 'Sunday'
+						],
+						'condition' => [
+							'field' => 'recurrence_type',
+							'operator' => '==',
+							'value' => 'weekly'
+						]
+					],
+					'recurrence_ends' => [
+						'type' => 'group',
+						'label' => 'Recurrence Ends',
+						'fields' => [
+							'type' => [
+								'type' => 'select',
+								'label' => 'Recurrence Ends',
+								'options' => [
+									'never' => 'Never',
+									'after' => 'After X occurrences',
+									'on' => 'On date'
+								]
+							],
+							'count' => [
+								'type' => 'number',
+								'label' => 'Number of occurrences',
+								'condition' => [
+									'field' => 'type',
+									'operator' => '==',
+									'value' => 'after'
+								]
+							],
+							'date' => [
+								'type' => 'date',
+								'label' => 'End date',
+								'condition' => [
+									'field' => 'type',
+									'operator' => '==',
+									'value' => 'on'
+								]
+							]
+						],
+					],
+
+					'rsvp_required' => [
+						'type' => 'true_false',
+						'label' => 'RSVP Required?'
+					],
+					'rsvp_limit' => [
+						'type' => 'number',
+						'label' => 'RSVP Limit',
+						'condition' => [
+							'field' => 'rsvp_required',
+							'operator' => '==',
+							'value' => 1
+						]
+					],
+					'rsvp_deadline' => [
+						'type' => 'datetime',
+						'label' => 'RSVP Deadline',
+						'condition' => [
+							'field' => 'rsvp_required',
+							'operator' => '==',
+							'value' => 1
+						]
+					],
+				]
+			]
+		];
+
+		foreach ($fields as $name => $config) {
+			$this->registrar->fields()->addField($name, $config);
+		}
+
+		$this->registrar->fields()->modifyField('post_status', 'options', [
+			'all' => [
+				'icon' => 'calendar',
+				'label' => 'Everything',
+			],
+			'future' => [
+				'label' => 'Upcoming',
+				'icon' => 'clock-clockwise',
+			],
+			'past' => [
+				'label' => 'Past',
+				'icon' => 'clock-counter-clockwise',
+			],
+			'repeat' => [
+				'label' => 'Recurring',
+				'icon' => 'repeat',
+			],
+			'draft' => [
+				'icon' => 'eye-closed',
+				'label' => 'Hidden',
+			],
+			'trash' => [
+				'label' => 'Scrapped',
+				'icon' => 'trash',
+			]
+		]);
+	}
+
+	public function handleCalendarLinks(string $url, WP_POST $post):string
+	{
+		if ($post->post_type === $this->postType) {
+			$get = ['year', 'month','day'];
+			$values = Meta::forPost($post->ID)->getAll($get);
+
+			foreach ($values as $key => $value) {
+				if (!empty($value)) {
+					$url = str_replace("%e$key%", $value, $url);
+				}
+			}
+			$doubleCheck = ['eyear', 'emonth', 'eday'];
+			foreach ($doubleCheck as $check) {
+				if (str_contains($url, $check)) {
+					$url = str_replace("/%e$check%", '', $url);
+				}
+			}
+		}
+		return $url;
+	}
+
+	public function handlePostTypeArchiveLinks(string $url, string $post_type):string
+	{
+		if ($post_type == $this->postType) {
+			$url = get_home_url(null, '/'.$this->registrar->registrar->rewrite['slug']??$this->slug.'/');
+		}
+		return $url;
+	}
+
+	public function addQueryVars(array $vars):array
+	{
+		$vars[] = 'eyear';
+		$vars[] = 'emonth';
+		$vars[] = 'eday';
+		return $vars;
+	}
+
+	public function addCalendarRewrites():void
+	{
+		add_rewrite_tag('%eyear%', '([^&]+)');
+		add_rewrite_tag('%emonth%', '([^&]+)');
+		add_rewrite_tag('%eday%', '([^&]+)');
+	}
+}
diff --git a/inc/registrar/helpers/MakeTrackChanges.php b/inc/registrar/helpers/MakeTrackChanges.php
new file mode 100644
index 0000000..4019f1f
--- /dev/null
+++ b/inc/registrar/helpers/MakeTrackChanges.php
@@ -0,0 +1,10 @@
+<?php
+namespace JVBase\registrar\helpers;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class MakeTrackChanges {
+
+}
diff --git a/inc/registrar/helpers/MakeVerification.php b/inc/registrar/helpers/MakeVerification.php
new file mode 100644
index 0000000..edf6d0e
--- /dev/null
+++ b/inc/registrar/helpers/MakeVerification.php
@@ -0,0 +1,10 @@
+<?php
+namespace JVBase\registrar\helpers;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class MakeVerification {
+
+}
diff --git a/inc/registry/CheckCustomTables.php b/inc/registry/CheckCustomTables.php
index bc5955e..eaf4dbc 100644
--- a/inc/registry/CheckCustomTables.php
+++ b/inc/registry/CheckCustomTables.php
@@ -6,6 +6,7 @@
 }
 
 use Exception;
+use JVBase\registrar\Registrar;
 
 class CheckCustomTables
 {
@@ -130,13 +131,13 @@
 				error_log("JVB: Error in notification section: " . $e->getMessage());
 			}
 
-			try {
+//			try {
 //				if (array_key_exists('dashboard', $this->JVB_SITE) && $this->JVB_SITE['dashboard']) {
-					$tables = array_merge($tables, $this->queueTables(), $this->errorLogTables());
+//					$tables = array_merge($tables, $this->queueTables(), $this->errorLogTables());
 //				}
-			} catch (Exception $e) {
-				error_log("JVB: Error in dashboard section: " . $e->getMessage());
-			}
+//			} catch (Exception $e) {
+//				error_log("JVB: Error in dashboard section: " . $e->getMessage());
+//			}
 
 			try {
 				if (array_key_exists('referrals', $this->JVB_SITE) && $this->JVB_SITE['referrals']) {
@@ -440,78 +441,78 @@
      * Table Definitions
      *****************************************************/
 
-    protected function queueTables():array
-    {
-
-        return [
-		'_operation_queue' => "(
-            `id` VARCHAR(64) NOT NULL,
-            `type` varchar(50) NOT NULL,
-            `user_id` {$this->userIDType} NOT NULL,
-
-            `request_data` JSON NOT NULL CHECK (JSON_VALID(request_data)),
-
-            `total_items` int(11) NOT NULL DEFAULT 1,
-            `processed_items` int(11) DEFAULT 0,
-            `failed_items` JSON,
-
-            `priority` ENUM('high', 'normal', 'low') DEFAULT 'normal',
-            `state` enum('pending', 'scheduled', 'processing', 'completed') DEFAULT 'pending',
-            `outcome` enum('pending', 'success', 'partial', 'merged', 'failed', 'failed_permanent') DEFAULT 'pending',
-
-			`retries` int(11) DEFAULT 0,
-            `last_error_hash` CHAR(32) DEFAULT NULL,
-            `error_message` text,
-
-			`scheduled_at` datetime DEFAULT NULL,
-            `started_at` datetime DEFAULT CURRENT_TIMESTAMP,
-            `completed_at` datetime DEFAULT NULL,
-
-            `metadata` JSON DEFAULT NULL,
-            `result` JSON,
-            `dependencies` JSON,
-            `merged_into` VARCHAR(64) DEFAULT NULL,
-
-            `user_dismissed` tinyint(1) DEFAULT 0,
-            `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
-    		`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
-			PRIMARY KEY (`id`),
-		   	KEY `idx_run_queue` (state, priority, scheduled_at),
-			KEY `idx_user_ops` (user_id, state),
-			KEY `idx_user_type_pending` (user_id, type, state),
-			KEY `idx_completed_at` (completed_at),
-			KEY `idx_processing_stuck` (`state`, `started_at`)
-            )",
-
-		'stats__operation_queue' => "(
-			`id` bigint unsigned AUTO_INCREMENT,
-			`date` date NOT NULL,
-			`type` varchar(50) NOT NULL,
-
-			`total_operations` int NOT NULL DEFAULT 0,
-			`successful_operations` int NOT NULL DEFAULT 0,
-			`partial_operations` int NOT NULL DEFAULT 0,
-			`failed_operations` int NOT NULL DEFAULT 0,
-			`failed_permanent_operations` int NOT NULL DEFAULT 0,
-
-			`total_items_processed` int NOT NULL DEFAULT 0,
-
-			`average_duration` float DEFAULT NULL,
-			`max_duration` int DEFAULT NULL,
-
-			`peak_queue_size` int NOT NULL DEFAULT 0,
-
-			`peak_memory_usage` int DEFAULT NULL,
-			`peak_cpu_usage` float DEFAULT NULL,
-
-			`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
-			PRIMARY KEY (`id`),
-			UNIQUE KEY (date, type),
-			KEY `date_idx` (date),
-			KEY `type_idx` (type)
-        )"
-        ];
-    }
+//    protected function queueTables():array
+//    {
+//
+//        return [
+//		'_operation_queue' => "(
+//            `id` VARCHAR(64) NOT NULL,
+//            `type` varchar(50) NOT NULL,
+//            `user_id` {$this->userIDType} NOT NULL,
+//
+//            `request_data` JSON NOT NULL CHECK (JSON_VALID(request_data)),
+//
+//            `total_items` int(11) NOT NULL DEFAULT 1,
+//            `processed_items` int(11) DEFAULT 0,
+//            `failed_items` JSON,
+//
+//            `priority` ENUM('high', 'normal', 'low') DEFAULT 'normal',
+//            `state` enum('pending', 'scheduled', 'processing', 'completed') DEFAULT 'pending',
+//            `outcome` enum('pending', 'success', 'partial', 'merged', 'failed', 'failed_permanent') DEFAULT 'pending',
+//
+//			`retries` int(11) DEFAULT 0,
+//            `last_error_hash` CHAR(32) DEFAULT NULL,
+//            `error_message` text,
+//
+//			`scheduled_at` datetime DEFAULT NULL,
+//            `started_at` datetime DEFAULT CURRENT_TIMESTAMP,
+//            `completed_at` datetime DEFAULT NULL,
+//
+//            `metadata` JSON DEFAULT NULL,
+//            `result` JSON,
+//            `dependencies` JSON,
+//            `merged_into` VARCHAR(64) DEFAULT NULL,
+//
+//            `user_dismissed` tinyint(1) DEFAULT 0,
+//            `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
+//    		`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
+//			PRIMARY KEY (`id`),
+//		   	KEY `idx_run_queue` (state, priority, scheduled_at),
+//			KEY `idx_user_ops` (user_id, state),
+//			KEY `idx_user_type_pending` (user_id, type, state),
+//			KEY `idx_completed_at` (completed_at),
+//			KEY `idx_processing_stuck` (`state`, `started_at`)
+//            )",
+//
+//		'stats__operation_queue' => "(
+//			`id` bigint unsigned AUTO_INCREMENT,
+//			`date` date NOT NULL,
+//			`type` varchar(50) NOT NULL,
+//
+//			`total_operations` int NOT NULL DEFAULT 0,
+//			`successful_operations` int NOT NULL DEFAULT 0,
+//			`partial_operations` int NOT NULL DEFAULT 0,
+//			`failed_operations` int NOT NULL DEFAULT 0,
+//			`failed_permanent_operations` int NOT NULL DEFAULT 0,
+//
+//			`total_items_processed` int NOT NULL DEFAULT 0,
+//
+//			`average_duration` float DEFAULT NULL,
+//			`max_duration` int DEFAULT NULL,
+//
+//			`peak_queue_size` int NOT NULL DEFAULT 0,
+//
+//			`peak_memory_usage` int DEFAULT NULL,
+//			`peak_cpu_usage` float DEFAULT NULL,
+//
+//			`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
+//			PRIMARY KEY (`id`),
+//			UNIQUE KEY (date, type),
+//			KEY `date_idx` (date),
+//			KEY `type_idx` (type)
+//        )"
+//        ];
+//    }
 
 	protected function errorLogTables():array
 	{
@@ -1519,7 +1520,7 @@
 		}
 
 		try {
-			$contentFields = jvbGetFields($type);
+			$contentFields = Registrar::getFieldsFor($type);
 
 			if (!$contentFields || empty($contentFields)) {
 				return;
diff --git a/inc/registry/TaxonomyRegistrar.php b/inc/registry/TaxonomyRegistrar.php
index 448d917..21f4450 100644
--- a/inc/registry/TaxonomyRegistrar.php
+++ b/inc/registry/TaxonomyRegistrar.php
@@ -1,6 +1,7 @@
 <?php
 namespace JVBase\registry;
 
+use JVBase\managers\CRUD;
 use JVBase\meta\Meta;
 use JVBase\meta\Registry;
 if (!defined('ABSPATH')) {
@@ -27,6 +28,7 @@
 		if ($this->config['is_content'] ?? false) {
 			$this->setupContentTaxonomyHooks();
 		}
+		add_filter('jvbDashboardPage', [$this, 'renderDashPage'], 10, 3);
 	}
 
 	public function register(): void
@@ -333,12 +335,15 @@
         return $data;
     }
 
-    /**
-     * Get custom table fields for this taxonomy
-     * @return array Field definitions
-     */
-    protected function getCustomTableFields():array
-    {
-        return jvbContentTaxonomiesTableFields($this->slug)['fields'] ?? [];
-    }
+	public function renderDashPage(string $content, string $page, string $slug):string
+	{
+		if ($slug === $this->slug) {
+			ob_start();
+			$crud = new CRUD($slug);
+			$crud->render();
+			return ob_get_clean();
+		}
+
+		return $content;
+	}
 }
diff --git a/inc/registry/_setup.php b/inc/registry/_setup.php
index c265fe7..e9248ef 100644
--- a/inc/registry/_setup.php
+++ b/inc/registry/_setup.php
@@ -1,16 +1,17 @@
 <?php
 
-require(JVB_DIR . '/inc/registry/providers/FieldProviderInterface.php');
-require(JVB_DIR . '/inc/registry/providers/CommonFieldProvider.php');
-require(JVB_DIR . '/inc/registry/providers/CalendarFieldProvider.php');
-require(JVB_DIR . '/inc/registry/providers/IntegrationFieldProvider.php');
-require(JVB_DIR . '/inc/registry/FieldRegistry.php');
-
-require(JVB_DIR . '/inc/registry/ContentRegistry.php');
-//The following sets everything up from /base/_setup.php
-require(JVB_DIR . '/inc/registry/PostTypeRegistrar.php');
-require(JVB_DIR . '/inc/registry/TaxonomyRegistrar.php');
-require(JVB_DIR . '/inc/registry/MakeCalendarType.php');
-require(JVB_DIR . '/inc/registry/UserRoleRegistrar.php');
-require(JVB_DIR . '/inc/registry/OptionsRegistry.php');
-require(JVB_DIR . '/inc/registry/CheckCustomTables.php');
+require(JVB_DIR . '/inc/registrar/_setup.php');
+//require(JVB_DIR . '/inc/registry/providers/FieldProviderInterface.php');
+//require(JVB_DIR . '/inc/registry/providers/CommonFieldProvider.php');
+//require(JVB_DIR . '/inc/registry/providers/CalendarFieldProvider.php');
+//require(JVB_DIR . '/inc/registry/providers/IntegrationFieldProvider.php');
+//require(JVB_DIR . '/inc/registry/FieldRegistry.php');
+//
+//require(JVB_DIR . '/inc/registry/ContentRegistry.php');
+////The following sets everything up from /base/_setup.php
+//require(JVB_DIR . '/inc/registry/PostTypeRegistrar.php');
+//require(JVB_DIR . '/inc/registry/TaxonomyRegistrar.php');
+//require(JVB_DIR . '/inc/registry/MakeCalendarType.php');
+//require(JVB_DIR . '/inc/registry/UserRoleRegistrar.php');
+//require(JVB_DIR . '/inc/registry/OptionsRegistry.php');
+//require(JVB_DIR . '/inc/registry/CheckCustomTables.php');
diff --git a/inc/registry/providers/IntegrationFieldProvider.php b/inc/registry/providers/IntegrationFieldProvider.php
index 07bf427..23f5535 100644
--- a/inc/registry/providers/IntegrationFieldProvider.php
+++ b/inc/registry/providers/IntegrationFieldProvider.php
@@ -1,31 +1,36 @@
 <?php
 namespace JVBase\registry\providers;
 
+use JVBase\utility\Features;
+
 if (!defined('ABSPATH')) {
 	exit;
 }
 
+/**
+ * @deprecated See JVBase\registrar\helpers\AddIntegrationFields.php
+ */
 class IntegrationFieldProvider implements FieldProviderInterface
 {
 	protected array $allowed = [];
 	public function __construct() {
 		$allowed = [];
-		if (jvbSiteUsesGMB()) {
+		if (Features::hasIntegration('gmb')) {
 			$allowed['gmb'] = 'Google My Business';
 		}
-		if (jvbSiteUsesFacebook()) {
+		if (Features::hasIntegration('facebook')) {
 			$allowed['facebook'] = 'Facebook';
 		}
-		if (jvbSiteUsesSquare()) {
+		if (Features::hasIntegration('square')) {
 			$allowed['square'] = 'Square';
 		}
-		if (jvbSiteUsesInstagram()) {
+		if (Features::hasIntegration('instagram')) {
 			$allowed['instagram'] = 'Instagram';
 		}
-		if (jvbSiteUsesBluesky()) {
+		if (Features::hasIntegration('bluesky')) {
 			$allowed['bluesky'] = 'BlueSky';
 		}
-		if (jvbSiteUsesHelcim()) {
+		if (Features::hasIntegration('helcim')) {
 			$allowed['helcim'] = 'Helcim';
 		}
 		$this->allowed = $allowed;
diff --git a/inc/rest/Rest.php b/inc/rest/Rest.php
index 6435ec8..406a3ab 100644
--- a/inc/rest/Rest.php
+++ b/inc/rest/Rest.php
@@ -2,6 +2,7 @@
 namespace JVBase\rest;
 
 use JVBase\managers\Cache;
+use JVBase\registrar\Registrar;
 use JVBase\utility\Features;
 use WP_REST_Request;
 use WP_REST_Response;
@@ -207,7 +208,7 @@
 
 		// Keep existing author filtering logic
 		$authorQuery = [];
-		foreach (jvbAuthorUsers() as $type) {
+		foreach (Registrar::getFeatured('can_create', 'user') as $type) {
 			if (array_key_exists($type, $data)) {
 				$artist_ids = array_map(
 					'absint',
@@ -240,7 +241,7 @@
 
 		//Handle random
 		if (array_key_exists('orderby', $data) && $data['orderby'] === 'random') {
-			$current_seed = jvbGetRandomSeed();
+			$current_seed = floor(time() / 1800);
 			$args['orderby'] = 'RAND(' . $current_seed . ')';
 			unset($args['order']);
 			return $args;
@@ -305,19 +306,19 @@
 		$content = jvbNoBase($post_type);
 
 		// Get config for this content type
-		$config = Features::getConfig($content);
-		if (!$config) {
+		$registrar = Registrar::getInstance($content);
+		if (!$registrar) {
 			return null;
 		}
 
 		// Check if this orderby is a custom order
-		$customOrders = $config['custom_order'] ?? [];
+		$customOrders = $registrar->custom_order??[];
 		if (empty($customOrders) || !isset($customOrders[$orderby])) {
 			return null;
 		}
 
 		// Get field definition
-		$fields = $config['fields'] ?? [];
+		$fields = $registrar->getFields() ?? [];
 		if (!isset($fields[$orderby])) {
 			return null;
 		}
@@ -456,15 +457,6 @@
 	}
 
 	/**
-	 * Check if content type exists
-	 */
-	protected function checkContent(string $content, bool $returnBool = false): string|bool
-	{
-		$result = JVB_CONTENT[$content] ?? JVB_TAXONOMY[$content] ?? JVB_USER[$content] ?? '';
-		return $returnBool ? ($result !== '') : $result;
-	}
-
-	/**
 	 * Check if user exists (cached)
 	 */
 	protected function checkUser(int $userId): bool
@@ -526,12 +518,8 @@
 	protected function isTimeline($args, $data):bool
 	{
 		$post_types = is_array($args['post_type']) ? $args['post_type'] : [$args['post_type']];
-		foreach ($post_types as $type) {
-			if (Features::forContent($type)->has('is_timeline')) {
-				return true;
-			}
-		}
-		return false;
+		$hasTimeline = array_map(function($item) { return jvbCheckBase($item); },Registrar::getFeatured('is_timeline', 'post'));
+		return !empty(array_intersect($post_types, $hasTimeline));
 	}
 	// =========================================================================
 	// SECURITY
diff --git a/inc/rest/RestRouteManager.php b/inc/rest/RestRouteManager.php
index e3122e6..f66f860 100644
--- a/inc/rest/RestRouteManager.php
+++ b/inc/rest/RestRouteManager.php
@@ -3,8 +3,7 @@
 
 use DateTime;
 use DateTimeZone;
-use JVBase\JVB;
-use JVBase\rest\RateLimiter;
+use JVBase\registrar\Registrar;
 use JVBase\managers\OperationQueue;
 use JVBase\managers\Cache;
 use JVBase\managers\NotificationManager;
@@ -284,6 +283,12 @@
 		});
 	}
 
+	/**
+	 * @deprecated
+	 * @param array $args
+	 * @param array $data
+	 * @return array
+	 */
 	protected function applyTaxonomyFilters(array $args, array $data):array
 	{
 		// Handle JSON-encoded taxonomy data
@@ -322,7 +327,7 @@
 
 		// Keep existing author filtering logic
 		$authorQuery = [];
-		foreach (jvbAuthorUsers() as $type) {
+		foreach (Registrar::getFeatured('can_create', 'user') as $type) {
 			if (array_key_exists($type, $data)) {
 				$artist_ids = array_map(
 					'absint',
@@ -352,7 +357,7 @@
 
 		//Handle random
         if (array_key_exists('orderby', $data) && $data['orderby'] === 'random') {
-            $current_seed = jvbGetRandomSeed();
+            $current_seed = floor(time() / 1800);
             $args['orderby'] = 'RAND(' . $current_seed . ')';
             unset($args['order']);
             return $args;
@@ -417,19 +422,19 @@
 		$content = jvbNoBase($post_type);
 
 		// Get config for this content type
-		$config = Features::getConfig($content);
+		$config = Registrar::getInstance($content);
 		if (!$config) {
 			return null;
 		}
 
 		// Check if this orderby is a custom order
-		$customOrders = $config['custom_order'] ?? [];
+		$customOrders = $config->custom_order ?? [];
 		if (empty($customOrders) || !isset($customOrders[$orderby])) {
 			return null;
 		}
 
 		// Get field definition
-		$fields = $config['fields'] ?? [];
+		$fields = $config->getFields()??[];
 		if (!isset($fields[$orderby])) {
 			return null;
 		}
@@ -478,12 +483,8 @@
 	protected function isTimeline($args, $data):bool
 	{
 		$post_types = is_array($args['post_type']) ? $args['post_type'] : [$args['post_type']];
-		foreach ($post_types as $type) {
-			if (Features::forContent($type)->has('is_timeline')) {
-				return true;
-			}
-		}
-		return false;
+		$areTimeline = array_map(function($type) { return BASE.$type; },Registrar::getFeatured('is_timeline', 'post'));
+		return !empty(array_intersect($post_types, $areTimeline));
 	}
 
     protected function applyDateFilters(array $args, array $data):array
diff --git a/inc/rest/_setup.php b/inc/rest/_setup.php
index 7ba414a..64fabde 100644
--- a/inc/rest/_setup.php
+++ b/inc/rest/_setup.php
@@ -1,4 +1,6 @@
 <?php
+
+use JVBase\registrar\Registrar;
 use JVBase\utility\Features;
 
 //NEW METHOD
@@ -49,11 +51,11 @@
 if (Features::forSite()->has('referrals')) {
 	require(JVB_DIR . '/inc/rest/routes/ReferralRoutes.php');
 }
-if (Features::anyContentHas('response') || Features::anyTaxonomyHas('response')) {
+if (!empty(Registrar::getFeatured('has_responses'))) {
 	require(JVB_DIR . '/inc/rest/routes/ResponseRoutes.php');
 }
 
-if (Features::anyContentHas('karma') || Features::anyTaxonomyHas('karma') || Features::anyUserHas('karma')) {
+if (!empty(Registrar::getFeatured('karma'))) {
 	require(JVB_DIR . '/inc/rest/routes/VoteRoutes.php');
 }
 
diff --git a/inc/rest/routes/ApprovalRoutes.php b/inc/rest/routes/ApprovalRoutes.php
index 6f9d590..d955825 100644
--- a/inc/rest/routes/ApprovalRoutes.php
+++ b/inc/rest/routes/ApprovalRoutes.php
@@ -3,6 +3,7 @@
 namespace JVBase\rest\routes;
 
 use JVBase\managers\CustomTable;
+use JVBase\registrar\Registrar;
 use JVBase\rest\PermissionHandler;
 use JVBase\rest\Rest;
 use JVBase\rest\Route;
@@ -42,20 +43,14 @@
 
     protected function initTypes():void
     {
-        $approvals = jvbApprovalTypes();
         $this->userTypes = [];
         $this->termTypes = [];
         if ($this->hasMemberApproval) {
-            $this->userTypes = array_filter(
-                array_keys($approvals),
-                function ($item) {
-                    return $item !== 'term';
-                }
-            );
+            $this->userTypes = Registrar::getFeatured('approve_new', 'user');
             $this->allTypes = $this->userTypes;
         }
-        if (jvbSiteHasTermApproval()) {
-            $this->termTypes = $approvals['term']??[];
+        if (Features::forSite()->has('term_approval')) {
+            $this->termTypes = Registrar::getFeatured('approve_new', 'term');
             $this->allTypes[] = 'term';
         }
     }
diff --git a/inc/rest/routes/ContentRoutes.php b/inc/rest/routes/ContentRoutes.php
index 0b9cea6..7f12b55 100644
--- a/inc/rest/routes/ContentRoutes.php
+++ b/inc/rest/routes/ContentRoutes.php
@@ -5,18 +5,15 @@
 use JVBase\managers\queue\executors\ContentExecutor;
 use JVBase\managers\queue\TypeConfig;
 use JVBase\meta\Meta;
+use JVBase\registrar\Registrar;
 use JVBase\rest\PermissionHandler;
 use JVBase\rest\Response;
 use JVBase\rest\Rest;
-use JVBase\managers\Cache;
 use JVBase\rest\Route;
-use JVBase\utility\Features;
 use WP_Post;
 use WP_Query;
-use WP_Error;
 use WP_REST_Request;
 use WP_REST_Response;
-use Exception;
 
 if (!defined('ABSPATH')) {
 	exit; // Exit if accessed directly
@@ -103,11 +100,12 @@
 	protected function initTimelineFields(string $content): void
 	{
 		$content = jvbNoBase($content);
-		if (!Features::forContent($content)->has('is_timeline')) {
+
+		$config = Registrar::getInstance($content);
+		if (!$config || !$config->hasFeature('is_timeline')) {
 			return;
 		}
-		$config = Features::getConfig($content);
-		$this->fields = $config['fields'];
+		$this->fields = $config->getFields();
 
 		$this->timelineSharedFields = $this->getTimelineSharedFields($content);
 		array_unshift($this->timelineSharedFields, 'post_thumbnail');
@@ -120,11 +118,12 @@
 	public function getTimelineUniqueFields(string $content): array
 	{
 		$content = jvbNoBase($content);
-		if (!Features::forContent($content)->has('is_timeline')) {
+		$registrar = Registrar::getInstance($content);
+		if (!$registrar || !$registrar->hasFeature('is_timeline')) {
 			return [];
 		}
-		$config = Features::getConfig($content);
-		$allFields = $config['fields'];
+
+		$allFields = $registrar->getFields();
 
 		return array_keys(array_filter($allFields, function ($field) {
 			if (array_key_exists('for_all', $field) && $field['for_all'] === true) {
@@ -137,14 +136,12 @@
 	public function getTimelineSharedFields(string $content): array
 	{
 		$content = jvbNoBase($content);
-		if (!Features::forContent($content)->has('is_timeline')) {
+		$registrar = Registrar::getInstance($content);
+		if (!$registrar || !$registrar->hasFeature('is_timeline')) {
 			return [];
 		}
-		$config = Features::getConfig($content);
-		if (!$config || empty($config)) {
-			return [];
-		}
-		$allFields = $config['fields'] ?? [];
+
+		$allFields = $registrar->getFields()??[];
 
 		return array_keys(array_filter($allFields, function ($field) {
 			if (!array_key_exists('for_all', $field) || $field['for_all'] === false) {
@@ -203,6 +200,7 @@
 	public function getContent(WP_REST_Request $request): WP_REST_Response
 	{
 		$params = $request->get_params();
+		error_log('getContent params: '.print_r($params, true));
 		$user_id = $params['user'];
 
 		$post_status = $params['status'];
@@ -225,12 +223,14 @@
 			'post_status' => $post_status
 		];
 		//Only top level posts for timeline types
-		if (Features::forContent($post_type)->has('is_timeline')) {
+		$registrar = Registrar::getInstance($post_type);
+
+		if ($registrar && $registrar->hasFeature('is_timeline')) {
 			$args['post_parent'] = 0;
 		}
 
 		//Calendar filters
-		if (Features::forContent($post_type)->has('is_calendar')) {
+		if ($registrar && $registrar->hasFeature('is_calendar')) {
 			$args = $this->applyCalendarFilters($args, $params);
 		}
 		$taxonomies = array_filter($params, function ($param) {
@@ -281,7 +281,7 @@
 		$query = new WP_Query($args);
 
 
-		$this->fields = jvbGetFields(str_replace('-', '_', $this->post_type));
+		$this->fields = Registrar::getFieldsFor(str_replace('-', '_', $this->post_type));
 		$this->taxonomies = $this->getTaxonomies($this->post_type);
 		$posts = array_map([$this, 'prepareItem'], $query->posts);
 
@@ -388,16 +388,19 @@
 	 */
 	protected function getTaxonomies(string $content): array
 	{
-		$taxonomy_for = jvbGlobalTaxonomyFor();
-		$out = [];
-		foreach ($taxonomy_for as $tax => $postTypes) {
-			if (in_array($content, $postTypes)) {
-				$out[BASE . $tax] = [
-					'label' => JVB_CONTENT[$content]['plural'],
-					'icon' => $tax,
-				];
-			}
+		$registrar = Registrar::getInstance($content);
+		if (!$registrar) {
+			return [];
 		}
+		$out = [];
+		foreach ($registrar->registrar->taxonomies as $tax) {
+			$taxReg = Registrar::getInstance($tax);
+			$out[jvbCheckBase($tax)] = [
+				'label'	=> $taxReg->getPlural(),
+				'icon'	=> $taxReg->getIcon()??jvbDefaultIcon()
+			];
+		}
+
 		return $out;
 	}
 
@@ -424,7 +427,8 @@
 	 */
 	protected function prepareItem(WP_Post $post, bool $skip = false, bool $fields = true): array
 	{
-		if (!$skip && Features::forContent($post->post_type)->has('is_timeline')) {
+		$registrar = Registrar::getInstance($post->post_type);
+		if (!$skip && $registrar && $registrar->hasFeature('is_timeline')) {
 			$this->initTimelineFields($post->post_type);
 			return $this->formatTimeline($post);
 		}
diff --git a/inc/rest/routes/ContentTermsRoutes.php b/inc/rest/routes/ContentTermsRoutes.php
index eee6b6b..d28cb60 100644
--- a/inc/rest/routes/ContentTermsRoutes.php
+++ b/inc/rest/routes/ContentTermsRoutes.php
@@ -3,6 +3,7 @@
 
 use JVBase\managers\queue\executors\ContentTermExecutor;
 use JVBase\managers\queue\TypeConfig;
+use JVBase\registrar\Registrar;
 use JVBase\rest\Rest;
 use JVBase\rest\Route;
 use JVBase\rest\PermissionHandler;
@@ -31,7 +32,7 @@
 class ContentTermsRoutes extends Rest
 {
 	protected string $taxonomy;
-	protected array $config;
+	protected Registrar $registrar;
 	protected ?CustomTable $historyTable = null;
 	protected ?CustomTable $requestsTable = null;
 
@@ -39,8 +40,8 @@
 	{
 		$this->taxonomy = jvbNoBase($taxonomy);
 
-		if ($taxonomy && isset(JVB_TAXONOMY[$this->taxonomy])) {
-			$this->config = JVB_TAXONOMY[$this->taxonomy];
+		if ($taxonomy !== '' && Registrar::getInstance($this->taxonomy)) {
+			$this->registrar = Registrar::getInstance($this->taxonomy);
 			$this->cacheName = $this->taxonomy;
 			parent::__construct();
 			$this->setupTables();
@@ -54,14 +55,15 @@
 	{
 		$registry = JVB()->queue()->registry();
 		$executor = new ContentTermExecutor();
-		$taxonomies = Features::getTypesWithFeature('is_content', 'taxonomy');
+		$taxonomies = Registrar::getFeatured('is_content', 'term');
 
 		foreach($taxonomies as $taxonomy) {
 			$registry->register("{$taxonomy}_update", new TypeConfig(
 				executor: $executor,
 			));
 
-			if (Features::forTaxonomy($taxonomy)->has('track_changes')) {
+			$registrar = Registrar::getInstance($taxonomy);
+			if ($registrar && $registrar->hasFeature('track_changes')) {
 				$registry->register("{$taxonomy}_member_add", new TypeConfig(
 					executor: $executor
 				));
@@ -76,9 +78,9 @@
 
 	protected function setupTables(): void
 	{
-		$content = $this->config['for_content'] ?? [];
+		$content = $this->registrar->registrar->for;
 
-		if (Features::forTaxonomy($this->taxonomy)->has('track_changes') && !empty($content)) {
+		if ($this->registrar->hasFeature('track_changes') && !empty($content)) {
 			foreach ($content as $contentType) {
 				$tableName = "history_{$contentType}_{$this->taxonomy}";
 				$this->historyTable = CustomTable::for($tableName);
@@ -86,7 +88,7 @@
 			}
 		}
 
-		if (Features::forTaxonomy($this->taxonomy)->has('verify_entry') && !empty($content)) {
+		if ($this->registrar->hasFeature('verify_entry') && !empty($content)) {
 			foreach ($content as $contentType) {
 				$tableName = "{$contentType}_{$this->taxonomy}_requests";
 				$this->requestsTable = CustomTable::for($tableName);
@@ -104,7 +106,7 @@
 
 	public function registerRoutes(): void
 	{
-		if (!Features::forTaxonomy($this->taxonomy)->has('is_content')) {
+		if (!$this->registrar->hasFeature('is_content')) {
 			return;
 		}
 
@@ -122,7 +124,7 @@
 			->register();
 
 		// Member management (if track_changes enabled)
-		if (Features::forTaxonomy($this->taxonomy)->has('track_changes')) {
+		if ($this->registrar->hasFeature('track_changes')) {
 			Route::for("{$base}/:term_id/members")
 				->get([$this, 'getMembers'])
 				->args([
@@ -146,7 +148,7 @@
 		}
 
 		// Membership requests (if verify_entry enabled)
-		if (Features::forTaxonomy($this->taxonomy)->has('verify_entry')) {
+		if ($this->registrar->hasFeature('verify_entry')) {
 			Route::for("{$base}/:term_id/requests")
 				->get([$this, 'getRequests'])
 				->args([
@@ -174,7 +176,7 @@
 		}
 
 		// Ownership/management (if is_ownable enabled)
-		if (Features::forTaxonomy($this->taxonomy)->has('is_ownable')) {
+		if ($this->registrar->hasFeature('is_ownable')) {
 			Route::for("{$base}/:term_id/permissions")
 				->post([$this, 'updatePermissions'])
 				->args([
diff --git a/inc/rest/routes/FavouritesRoutes.php b/inc/rest/routes/FavouritesRoutes.php
index 9690a53..ae49baf 100644
--- a/inc/rest/routes/FavouritesRoutes.php
+++ b/inc/rest/routes/FavouritesRoutes.php
@@ -3,6 +3,7 @@
 
 use JVBase\managers\Cache;
 use JVBase\managers\CustomTable;
+use JVBase\registrar\Registrar;
 use JVBase\rest\PermissionHandler;
 use JVBase\rest\Response;
 use JVBase\rest\Rest;
@@ -41,7 +42,7 @@
 		$this->sharedListsCache = Cache::for('sharedLists')->connect('favourites', true);
 		$this->favouritesCache = Cache::for('allFavourites')->connect('favourites', true);
 
-		$this->valid_types = array_keys(array_merge(JVB_CONTENT, JVB_TAXONOMY));
+		$this->valid_types = array_merge(Registrar::getRegistered('post'), Registrar::getRegistered('term'));
 
 		// Initialize CustomTable instances
 		$this->favourites = CustomTable::for('favourites');
@@ -847,7 +848,7 @@
 
 		$args = array_merge($args, [
 			'page' => max(1, absint($data['page'] ?? 1)),
-			'content' => $this->checkContent($data['content'] ?? 'all')
+			'content' => Registrar::getInstance($data['content']) ? $data['content'] : 'all',
 		]);
 
 		return $this->applyOrderFilters($args, $data);
diff --git a/inc/rest/routes/FeedRoutes.php b/inc/rest/routes/FeedRoutes.php
index 81d31d7..f1b6bb2 100644
--- a/inc/rest/routes/FeedRoutes.php
+++ b/inc/rest/routes/FeedRoutes.php
@@ -2,10 +2,10 @@
 namespace JVBase\rest\routes;
 
 use JVBase\meta\Meta;
+use JVBase\registrar\Registrar;
 use JVBase\rest\Rest;
 use JVBase\integrations\Umami;
 use JVBase\rest\Route;
-use JVBase\utility\Checker;
 use JVBase\utility\Features;
 use WP_Query;
 use WP_Post;
@@ -21,7 +21,7 @@
 {
 	protected int $per_page = 36;
 	protected ?Umami $tracker = null;
-	protected ?Checker $checker = null;
+
 	protected ?array $fields = null;
 	protected ?array $timelineSharedFields = null;
 	protected ?array $timelineUniqueFields = null;
@@ -44,8 +44,6 @@
 
 	public function init():void
 	{
-		$this->checker = Checker::getInstance();
-
 		if (Features::hasIntegration('umami')) {
 			$this->tracker = JVB()->connect('umami');
 		}
@@ -133,35 +131,34 @@
 		return $this->cache->remember(
 			$postID,
 			function() use ($postID, $type, $metaType, $post, $skip) {
-				$config = null;
+				$registrar = null;
 				switch ($metaType) {
 					case 'post':
-						$config = JVB_CONTENT[$type];
-
+						$registrar = Registrar::getInstance($type);
 						$meta = Meta::forPost($postID);
-						if (!$skip && array_key_exists('is_timeline', $config) && $config['is_timeline']) {
+						if (!$skip && $registrar->isTimeline()) {
 							return $this->formatTimeline($postID, $post);
 						}
 						break;
 					case 'term':
 
 						$meta = Meta::forTerm($postID);
-						$config = JVB_TAXONOMY[$type];
+						$registrar = Registrar::getInstance($type);
 						break;
 					case 'user':
 						$meta = Meta::forUser($postID);
-						$config = JVB_USER[$type];
+						$registrar = Registrar::getInstance($type);
 						break;
 				}
-				if (!$config) {
+				if (!$registrar) {
 					return [];
 				}
-				$fields = $config['fields'];
+				$fields = $registrar->getFields();
 
 				//Allow custom filtering for public fields
-				if (array_key_exists('feed', $config) && array_key_exists('fields', $config['feed'])) {
-					$fields = array_filter($fields, function($field) use ($config) {
-						return in_array($field, $config['feed']['fields']);
+				if (!empty($registrar->getConfig('feed')['fields'])) {
+					$fields = array_filter($fields, function($field) use ($registrar) {
+						return in_array($field, $registrar->getConfig('feed')['fields']);
 					}, ARRAY_FILTER_USE_KEY);
 				}
 
@@ -192,7 +189,10 @@
 
 				$out['id'] = $postID;
 				$out['content'] = $type;
-				$out['icon'] = $config['icon']??jvbDefaultIcon();
+				$out['icon'] = $registrar->getIcon()??jvbDefaultIcon();
+				if ($out['icon'] === '') {
+					$out['icon'] = jvbDefaultIcon();
+				}
 
 				if ($this->tracker) {
 					$args = ($metaType === 'post') ? ['owner_id' => $post->post_author] : [];
@@ -203,7 +203,8 @@
 
 				switch ($metaType) {
 					case 'term':
-						$owner = (in_array($type, jvbContentTaxonomies()) ? $meta->getValue('owner') : null);
+
+						$owner = $registrar->hasFeature('is_content') ? $meta->get('owner') : null;
 						if (!is_null($owner)) {
 							$out['user_id'] = $owner;
 						}
@@ -227,12 +228,11 @@
 
 	protected function initTimelineFields(string $content):void
 	{
-		$content = jvbNoBase($content);
-		if (!Features::forContent($content)->has('is_timeline')){
+		$registrar = Registrar::getInstance($content);
+		if (!$registrar || !$registrar->hasFeature('is_timeline')){
 			return;
 		}
-		$config = Features::getConfig($content);
-		$this->fields = $config['fields'];
+		$this->fields = $registrar->getFields();
 
 		$this->timelineSharedFields = array_keys(array_filter($this->fields, function ($field) {
 			if (!array_key_exists('for_all', $field) || $field['for_all'] === false){
@@ -293,14 +293,12 @@
 			if (empty($value)) {
 				continue;
 			}
-			if (!array_key_exists($key, JVB_TAXONOMY)) {
+
+			$registrar = Registrar::getInstance($key);
+			if (!$registrar || $registrar->registrar->public === false){
 				continue;
 			}
 
-			$taxConfig = JVB_TAXONOMY[$key];
-			if (isset($taxConfig['public']) && $taxConfig['public'] === false) {
-				continue;
-			}
 			$terms = array_map('absint', explode(',', $value));
 			$terms = array_filter($terms); // Remove 0 values
 
@@ -345,8 +343,9 @@
 			$userLink,
 			function () use ($userLink, $author) {
 				$label = jvbUserRole($author);
-				if (array_key_exists($label, JVB_USER)) {
-					$label = JVB_USER[$label]['label'];
+				$registrar = Registrar::getInstance($label);
+				if ($registrar) {
+					$label = $registrar->getSingular();
 				} else {
 					$label = 'Artist';
 				}
@@ -363,16 +362,17 @@
 
 	protected function getTaxonomies(int $postID, string $content): array
 	{
-		$taxonomies = jvbTaxonomiesForContent($content);
+		$registrar = Registrar::getInstance($content)??false;
+		$taxonomies = $registrar->registrar->taxonomies;
 		$out = [];
 		foreach ($taxonomies as $tax) {
 			$terms = get_the_terms($postID, $tax);
 			$t = [];
 			if ($terms && !is_wp_error($terms)) {
-				$config = jvbNoBase($tax);
+				$config = Registrar::getInstance($tax);
 				$out[] = [
-					'icon' => $config,
-					'title' => JVB_TAXONOMY[$config]['plural'],
+					'icon' => $config->getIcon(),
+					'title' => $config->getPlural(),
 					'terms' => array_map(function ($term) use ($tax, $postID, $content) {
 						$item = $this->cache->remember(
 							$term->term_id,
@@ -402,8 +402,7 @@
 		$data = $request->get_params();
 		$args = [
 			'post_type' => (array_key_exists($data['content'], $this->buildFeedTypesConfig())) ?
-				BASE . $data['content'] :
-				BASE . array_key_first(JVB_CONTENT),
+				jvbCheckBase($data['content']) : null,
 			'paged' => intval($data['page'] ?? 1),
 			'posts_per_page' => $this->per_page,
 		];
@@ -492,15 +491,8 @@
 		// Extract key and value
 		$key = array_keys($data['highlight'])[0] ?? false;
 		$value = array_values($data['highlight'])[0] ?? false;
-		error_log('Highlighted item: ' . $key);
-		error_log('Highlighted item: ' . $value);
-		error_log('No Single Content Types: ' . print_r(jvbNoSingleContentTypes(), true));
-		error_log('Page: ' . print_r($data['paged'], true));
-		if (in_array($key, jvbNoSingleContentTypes()) && $value && $data['paged'] === 1) {
-			error_log('Formatted Highlighted item: ' . print_r($this->formatItem($value), true));
-			error_log('Items: ' . print_r($items, true));
+		if ($key && $data['paged'] === 1) {
 			array_unshift($items, $this->formatItem($value));
-			error_log('Items after unshift: ' . print_r($items, true));
 		}
 		return $items;
 	}
@@ -517,37 +509,29 @@
 			return $args;
 		}
 
+		$registrar = Registrar::getInstance($context['type']);
 		switch (true) {
-			case contentIsJVBUserType($context['type']):
+			case $registrar->hasFeature('profile_link'):
 				$args['author'] = (int)get_post_meta($context['id'], BASE . 'link', true);
 				break;
-			case taxIsJVBContentTax($context['type']):
+			case $registrar->getType() === 'term' && $registrar->hasFeature('is_content'):
 				$args['post_type'] = is_array($args['post_type'])
 					? $args['post_type']
 					: explode(',', $args['post_type']);
 
 				// Check if filtering global feed content
-				if (in_array($context['type'], jvbGlobalFeedContentTaxonomies())) {
+				if (in_array(jvbNoBase($context['type']), Registrar::getFeatured('is_content', 'term'))) {
 					// Global: show posts from any content type with this taxonomy
-					$for_content = JVB_TAXONOMY[$context['type']]['for_content'] ?? [];
-					if (empty($for_content)) {
-						// Fall back to any content that has this taxonomy registered
-						$for_content = array_keys(
-							array_filter(
-								JVB_CONTENT,
-								fn($c) => in_array($context['type'], $c['taxonomies'] ?? [])
-							)
-						);
-					}
+					$for_content = Registrar::getInstance($context['type'])->registrar->for ?? [];
 
 					// Convert to full post types with BASE prefix
-					$post_types = array_map(fn($type) => BASE . $type, $for_content);
+					$post_types = array_map(fn($type) => jvbCheckBase($type), $for_content);
 
 					// Filter to only show_feed content types
-					$show_feed_types = Features::getTypesWithFeature('show_feed', 'content');
+					$show_feed_types = Registrar::getFeatured('show_feed', 'post');
 					$args['post_type'] = array_intersect(
 						$post_types,
-						array_map(fn($type) => BASE . $type, $show_feed_types)
+						array_map(fn($type) => jvbCheckBase($type), $show_feed_types)
 					);
 				}
 
@@ -610,11 +594,11 @@
 	{
 		$postType = is_array($args['post_type']) ? $args['post_type'][0] : $args['post_type'];
 		$slug = jvbNoBase($postType);
-
-		if (Features::forContent($slug)->has('is_timeline')) {
+		$registrar = Registrar::getInstance($slug);
+		if ($registrar && $registrar->hasFeature('is_timeline')) {
 			$args['post_parent'] = 0;
 		}
-		if (in_array($slug, Features::getTypesWithFeature('is_content', 'taxonomy'))) {
+		if ($registrar && $registrar->hasFeature('is_content')) {
 			return $this->handleContentTaxonomies($args);
 		}
 		$args['fields'] = 'ids';
@@ -1141,15 +1125,6 @@
 		}
 	}
 
-	/**
-	 * Get custom table fields for a taxonomy
-	 * @param string $taxonomy Taxonomy type
-	 * @return array Field definitions
-	 */
-	protected function getCustomTableFields(string $taxonomy): array
-	{
-		return jvbContentTaxonomiesTableFields($taxonomy)['fields'] ?? [];
-	}
 
 	/**
 	 * Get available feed types (for block editor)
@@ -1178,35 +1153,32 @@
 	 */
 	protected function buildFeedTypesConfig(): array
 	{
-		if (!$this->checker) {
-			$this->checker = Checker::getInstance();
-		}
 		return $this->cache->remember(
 			'contentTypes',
 			function () {
 				$config = [];
 
 				// Get content types with show_feed
-				$contentTypes = Features::getTypesWithFeature('show_feed', 'content');
+				$contentTypes = Registrar::getFeatured('show_feed', 'post');
 				foreach ($contentTypes as $slug) {
 					$this->cache->tag('content:'.$slug);
-					$contentConfig = JVB_CONTENT[$slug] ?? null;
-					if (!$contentConfig) continue;
+					$registrar = Registrar::getInstance($slug);
+					if (!$registrar) continue;
 
 					$config[$slug] = [
 						'type' => 'content',
-						'singular' => $contentConfig['singular'] ?? ucfirst($slug),
-						'plural' => $contentConfig['plural'] ?? ucfirst($slug) . 's',
-						'icon' => $slug,
-						'taxonomies' => $this->checker->getTaxonomiesForContent($slug),
+						'singular' => $registrar->getSingular(),
+						'plural' => $registrar->getPlural(),
+						'icon' => $registrar->getIcon(),
+						'taxonomies' => $registrar->registrar->taxonomies,
 					];
 				}
 
 				// Get taxonomies with show_feed (content taxonomies)
-				$taxonomies = Features::getTypesWithFeature('show_feed', 'taxonomy');
+				$taxonomies = Registrar::getFeatured('show_feed', 'term');
 				foreach ($taxonomies as $slug) {
-					$taxConfig = JVB_TAXONOMY[$slug] ?? null;
-					if (!$taxConfig || !($taxConfig['is_content'] ?? false)) {
+					$registrar = Registrar::getInstance($slug);
+					if (!$registrar || !($registrar->hasFeature('is_content') ?? false)) {
 						continue;
 					}
 
@@ -1214,11 +1186,11 @@
 
 					$config[$slug] = [
 						'type' => 'taxonomy',
-						'singular' => $taxConfig['singular'] ?? ucfirst($slug),
-						'plural' => $taxConfig['plural'] ?? ucfirst($slug) . 's',
-						'icon' => $slug,
+						'singular' => $registrar->getSingular(),
+						'plural' => $registrar->getPlural(),
+						'icon' => $registrar->getIcon(),
 						'taxonomies' => [], // Content taxonomies don't have sub-taxonomies
-						'for_content' => $taxConfig['for_content'] ?? [],
+						'for_content' => $registrar->registrar->for ?? []
 					];
 				}
 
diff --git a/inc/rest/routes/LoginRoutes.php b/inc/rest/routes/LoginRoutes.php
index 8ab30ce..10e448f 100644
--- a/inc/rest/routes/LoginRoutes.php
+++ b/inc/rest/routes/LoginRoutes.php
@@ -1,6 +1,7 @@
 <?php
 namespace JVBase\rest\routes;
 
+use JVBase\registrar\Registrar;
 use JVBase\rest\Rest;
 use JVBase\rest\Route;
 use JVBase\utility\Features;
@@ -739,9 +740,7 @@
 		if (Features::forSite()->has('favourites')) {
 			$nonces['favourites'] = wp_create_nonce('favourites-'.$userID);
 		}
-		if (Features::anyContentHas('karma') ||
-			Features::anyTaxonomyHas('karma') ||
-			Features::anyUserHas('karma')) {
+		if (!empty(Registrar::getFeatured('karma'))) {
 			$nonces['votes'] = wp_create_nonce('votes-'.$userID);
 		}
 		if (Features::forSite()->has('notifications')) {
diff --git a/inc/rest/routes/NewsRoutes.php b/inc/rest/routes/NewsRoutes.php
index 8e30b42..6a1fee5 100644
--- a/inc/rest/routes/NewsRoutes.php
+++ b/inc/rest/routes/NewsRoutes.php
@@ -198,7 +198,7 @@
     {
         if (array_key_exists('orderby', $data) && $data['orderby'] === 'random') {
             // Handle random ordering
-            $current_seed = jvbGetRandomSeed();
+            $current_seed = floor(time() / 1800);
             $args['orderby'] = 'RAND(' . $current_seed . ')';
             unset($args['order']);
         } else {
diff --git a/inc/rest/routes/NotificationsRoutes.php b/inc/rest/routes/NotificationsRoutes.php
index ee85aeb..1b65a1f 100644
--- a/inc/rest/routes/NotificationsRoutes.php
+++ b/inc/rest/routes/NotificationsRoutes.php
@@ -3,6 +3,7 @@
 
 use JVBase\managers\Cache;
 use JVBase\managers\CustomTable;
+use JVBase\registrar\Registrar;
 use JVBase\rest\Rest;
 use JVBase\rest\Route;
 use WP_REST_Request;
@@ -554,7 +555,7 @@
 				$statusCondition = $wpdb->prepare("a.status = %s", $status);
 			}
 
-			$approvals = jvbApprovalTypes();
+			$approvals = Registrar::getFeatured('approve_new');
 			foreach ($approvals as $type => $config) {
 				$table = $wpdb->prefix . BASE . 'approval_' . $type . 'requests';
 				$votes = $wpdb->prefix . BASE . 'approval_' . $type . 'votes';
diff --git a/inc/rest/routes/OptionsRoutes.php b/inc/rest/routes/OptionsRoutes.php
index 355db8c..9b3bf54 100644
--- a/inc/rest/routes/OptionsRoutes.php
+++ b/inc/rest/routes/OptionsRoutes.php
@@ -1,6 +1,7 @@
 <?php
 namespace JVBase\rest\routes;
 
+use JVBase\registrar\Registrar;
 use JVBase\rest\PermissionHandler;
 use JVBase\rest\Rest;
 use JVBase\managers\Cache;
@@ -81,7 +82,7 @@
 		];
 
 		$meta = Meta::forOptions('options');
-		$fields = jvbGetFields('options');
+		$fields = Registrar::getFieldsFor('options');
 
 		$allowedFields = array_filter($data,
 		function ($key) use ($fields) {
diff --git a/inc/rest/routes/SEORoutes.php b/inc/rest/routes/SEORoutes.php
index 52602a6..786452a 100644
--- a/inc/rest/routes/SEORoutes.php
+++ b/inc/rest/routes/SEORoutes.php
@@ -1,6 +1,7 @@
 <?php
 namespace JVBase\rest\routes;
 
+use JVBase\registrar\Registrar;
 use JVBase\rest\Rest;
 use JVBase\managers\Cache;
 use JVBase\managers\SEO\ConfigManager;
@@ -22,13 +23,13 @@
  */
 class SEORoutes extends Rest
 {
-	protected SchemaBuilder $registry;
+//	protected SchemaBuilder $registry;
 
 	public function __construct()
 	{
 		$this->cacheName = 'schema';
 		parent::__construct();
-		$this->registry = SchemaBuilder::getInstance();
+//		$this->registry = SchemaBuilder::getInstance();
 	}
 
 	/**
@@ -264,6 +265,6 @@
 		}
 
 		// Check if it's a valid content/taxonomy/user type
-		return $this->checkContent($context, true);
+		return (bool)Registrar::getInstance($context);
 	}
 }
diff --git a/inc/rest/routes/ShopRoutes.php b/inc/rest/routes/ShopRoutes.php
index 83132a0..923a74a 100644
--- a/inc/rest/routes/ShopRoutes.php
+++ b/inc/rest/routes/ShopRoutes.php
@@ -4,6 +4,7 @@
 use JVBase\JVB;
 use JVBase\managers\ImageGenerator;
 use JVBase\managers\UploadManager;
+use JVBase\registrar\Registrar;
 use JVBase\rest\RestRouteManager;
 use JVBase\meta\Meta;
 use WP_REST_Request;
@@ -150,7 +151,7 @@
         }
 
         $meta = Meta::forTerm($shop);
-		$allowed = jvbGetFields('shop', 'term');
+		$allowed = Registrar::getFieldsFor('shop');
 		$setData = array_filter(
 			$data,
 			function ($key) use ($allowed) {
diff --git a/inc/rest/routes/UploadRoutes.php b/inc/rest/routes/UploadRoutes.php
index 86ecc06..e46aa84 100644
--- a/inc/rest/routes/UploadRoutes.php
+++ b/inc/rest/routes/UploadRoutes.php
@@ -4,12 +4,12 @@
 use JVBase\managers\queue\executors\UploadExecutor;
 use JVBase\managers\queue\mergers\UploadMerger;
 use JVBase\managers\queue\TypeConfig;
+use JVBase\registrar\Registrar;
 use JVBase\rest\PermissionHandler;
 use JVBase\rest\Rest;
 use JVBase\meta\Meta;
 use JVBase\managers\UploadManager;
 use JVBase\rest\Route;
-use JVBase\utility\Features;
 use WP_REST_Request;
 use WP_REST_Response;
 use WP_Error;
@@ -148,7 +148,7 @@
 				// Post Type/Taxonomy
 				case 'content':
 					$key = str_replace('-', '_', $key);
-					if ($value === 'options' || array_key_exists($value, JVB_CONTENT) || Features::forTaxonomy($key)->has('is_content')) {
+					if ($value === 'options' || array_key_exists($value, Registrar::getRegistered('post')) || Registrar::getInstance($key)->hasFeature('is_content')??false) {
 						$args['content'] = $value;
 					}
 					break;
@@ -1090,9 +1090,9 @@
 		if (!empty($args['content']) && !empty($args['field_name'])) {
 			$content_type = $args['content'];
 			$field_name = $args['field_name'];
-
-			if (array_key_exists($content_type, JVB_CONTENT)) {
-				$content_fields = JVB_CONTENT[$content_type]['fields'] ?? [];
+			$registrar = Registrar::getInstance($content_type);
+			if ($registrar) {
+				$content_fields = $registrar->getFields();
 				if (array_key_exists($field_name, $content_fields)) {
 					$field_def = $content_fields[$field_name];
 
diff --git a/inc/rest/routes/VoteRoutes.php b/inc/rest/routes/VoteRoutes.php
index b79ec08..7f55f1d 100644
--- a/inc/rest/routes/VoteRoutes.php
+++ b/inc/rest/routes/VoteRoutes.php
@@ -2,10 +2,10 @@
 namespace JVBase\rest\routes;
 
 use JVBase\managers\CustomTable;
+use JVBase\registrar\Registrar;
 use JVBase\rest\Response;
 use JVBase\rest\Rest;
 use JVBase\rest\Route;
-use JVBase\utility\Features;
 use WP_REST_Request;
 use WP_REST_Response;
 use WP_Error;
@@ -58,7 +58,8 @@
     public function handleVote(WP_REST_Request $request):WP_REST_Response
     {
         $content = sanitize_text_field($request->get_param('content')??'');
-		if ((!Features::forContent($content)->has('karma') && !Features::forTaxonomy($content)->has('karma') && !Features::forUser($content)->has('karma'))) {
+		$registrar = Registrar::getInstance($content);
+		if (!$registrar || !$registrar->hasFeature('karma')) {
 			return Response::validationError(['message' => __('Invalid content', 'jvb')]);
 		}
 
@@ -75,12 +76,7 @@
 
         $operation = sanitize_text_field($request->get_param('id'));
 
-		$type = match(true) {
-			array_key_exists($content, JVB_CONTENT) => 'post',
-			array_key_exists($content, JVB_TAXONOMY) => 'term',
-			array_key_exists($content, JVB_USER) => 'user',
-			default => false
-		};
+		$type = Registrar::getInstance($content)->getType()??false;
 		if (!$type) {
 			return Response::validationError(['message' => __('Invalid content type', 'jvb')]);
 		}
@@ -282,22 +278,20 @@
 
 		$votes = [];
 
-		foreach (jvbGlobalKarma() as $type => $content_types) {
-			foreach ($content_types as $content_type) {
-				$table = CustomTable::for('karma_' . $content_type);
+		foreach (Registrar::getFeatured('has_karma') as $type) {
+			$table = CustomTable::for('karma_' . $type);
 
-				// Skip if table doesn't exist
-				global $wpdb;
-				if ($wpdb->get_var("SHOW TABLES LIKE '{$table->getFullTableName()}'") != $table->getFullTableName()) {
-					continue;
-				}
+			// Skip if table doesn't exist
+			global $wpdb;
+			if ($wpdb->get_var("SHOW TABLES LIKE '{$table->getFullTableName()}'") != $table->getFullTableName()) {
+				continue;
+			}
 
-				$results = $table->where(['user_id' => $user])->getResults();
+			$results = $table->where(['user_id' => $user])->getResults();
 
-				if (!empty($results)) {
-					foreach ($results as $vote) {
-						$votes[$content_type][$vote->item_id] = $vote->vote;
-					}
+			if (!empty($results)) {
+				foreach ($results as $vote) {
+					$votes[$type][$vote->item_id] = $vote->vote;
 				}
 			}
 		}
diff --git a/inc/ui/CRUDSkeleton.php b/inc/ui/CRUDSkeleton.php
index c32dcce..c8eeda1 100644
--- a/inc/ui/CRUDSkeleton.php
+++ b/inc/ui/CRUDSkeleton.php
@@ -3,6 +3,7 @@
 
 use JVBase\managers\UserTermsManager;
 use JVBase\meta\Form;
+use JVBase\registrar\Registrar;
 use WP_User;
 
 if (!defined('ABSPATH')) {
@@ -128,6 +129,7 @@
 	protected array $customDateRanges = [];
 	protected array $additionalClasses = [];
 
+	protected Registrar $registrar;
 	public function __construct() {
 		$this->icon = jvbDefaultIcon();
 		$this->user = wp_get_current_user();
@@ -148,6 +150,7 @@
 	 */
 	public function content(string $type, string $singular, string $plural): self {
 		$this->dataType = $type;
+		$this->registrar = Registrar::getInstance($type);
 		$this->singular = $singular;
 		$this->plural = $plural;
 		return $this;
@@ -213,12 +216,13 @@
 	 */
 	public function addTaxonomyFilter(array $taxonomies, ?string $limit = null): self {
 		foreach($taxonomies as $taxonomy) {
+			$registrar = Registrar::getInstance($taxonomy);
 			$this->taxonomies[$taxonomy] = [
 				'type'	=> 'taxonomy',
 				'taxonomy'=> $taxonomy,
 				'limit'	=> $limit,
-				'label'	=> JVB_TAXONOMY[$taxonomy]['plural']??'',
-				'icon'	=> JVB_TAXONOMY[$taxonomy]['icon']??''
+				'label'	=> $registrar->getPlural(),
+				'icon'	=> $registrar->getIcon()
 			];
 		}
 
@@ -228,7 +232,7 @@
 	protected function taxConfig(string $taxonomy, string $label = ''):array
 	{
 		$isVerified = jvbUserIsVerified();
-		$label = ($label === '') ? JVB_TAXONOMY[$taxonomy]['plural'] : $label;
+		$label = ($label === '') ? Registrar::getInstance($taxonomy)->getPlural() : $label;
 		return [
 			'type'		=> 'taxonomy',
 			'label'		=> $label,
@@ -973,9 +977,11 @@
 					<option value="<?=$control?>"<?=$disabled?>><?=$label?></option>
 					<?php
 				}
-				foreach ($this->taxonomies as $taxonomy => $config) {
+				foreach ($this->taxonomies as $taxonomy) {
+					$registrar = Registrar::getInstance($taxonomy);
+					if (!$registrar) continue;
 					?>
-					<option value="tax-<?=$taxonomy?>" data-type="selector" data-single="<?=JVB_TAXONOMY[$taxonomy]['singular']?>" data-plural="<?=JVB_TAXONOMY[$taxonomy]['plural']?>" data-taxonomy="<?=$taxonomy?>">Add to <?= JVB_TAXONOMY[$taxonomy]['singular']??$config['label'] ?></option>
+					<option value="tax-<?=$taxonomy?>" data-type="selector" data-single="<?=$registrar->getSingular()?>" data-plural="<?=$registrar->getPlural()?>" data-taxonomy="<?=$taxonomy?>">Add to <?= $registrar->getSingular() ?></option>
 					<?php
 				}
 				?>
@@ -1549,7 +1555,8 @@
 
 				if (!empty($this->sections)) {
 					$tabs = [];
-					foreach ($this->sections as $slug => $config) {
+					foreach ($this->sections as $config) {
+						$slug = $config['slug'];
 						$section = [];
 						if (array_key_exists('icon', $config)) {
 							$section = [
@@ -1561,10 +1568,6 @@
 							'content' => '',
 							'description' => $config['description']??'',
 						], $section);
-						$icon = jvbSectionIcon($slug);
-						if ($icon !== '') {
-							$tabs[$slug]['icon'] = $icon;
-						}
 					}
 				} else {
 					$tabs = false;
@@ -1621,6 +1624,7 @@
 						$section = (array_key_exists('section', $config)) ? $config['section'] : 'basic';
 						$tabs[$section]['content'] .= Form::render($n, '', $config);
 					} else {
+						jvbDump($config, $n);
 						echo Form::render($n, '', $config);
 					}
 				}
diff --git a/inc/utility/Checker.php b/inc/utility/Checker.php
index 90804fa..2cb420f 100644
--- a/inc/utility/Checker.php
+++ b/inc/utility/Checker.php
@@ -5,6 +5,7 @@
 	exit;
 }
 /**
+ * @deprecated Use Registrar.php directly
  * Centralized registry for all content types, taxonomies, and user roles
  * Provides a single source of truth and caching layer
  */
@@ -39,9 +40,6 @@
 	private function initialize(): void
 	{
 		// Build initial caches
-		$this->buildContentCache();
-		$this->buildTaxonomyCache();
-		$this->buildUserRoleCache();
 		$this->buildRelationships();
 
 		// Set up WordPress hooks for cache invalidation
@@ -151,49 +149,6 @@
 		};
 	}
 
-	/**
-	 * Build content type cache
-	 */
-	private function buildContentCache(): void
-	{
-		$this->cache[self::CACHE_CONTENT] = JVB_CONTENT;
-
-		// Add computed properties
-		foreach ($this->cache[self::CACHE_CONTENT] as $slug => &$config) {
-			$config['_slug'] = $slug;
-			$config['_post_type'] = BASE . $slug;
-			$config['_supports_dashboard'] = $this->computesDashboardSupport($config);
-			$config['_is_user_type'] = $this->computesUserType($config);
-		}
-	}
-
-	/**
-	 * Build taxonomy cache
-	 */
-	private function buildTaxonomyCache(): void
-	{
-		$this->cache[self::CACHE_TAXONOMIES] = JVB_TAXONOMY;
-
-		foreach ($this->cache[self::CACHE_TAXONOMIES] as $slug => &$config) {
-			$config['_slug'] = $slug;
-			$config['_taxonomy'] = BASE . $slug;
-			$config['_is_hierarchical'] = $config['hierarchical'] ?? true;
-		}
-	}
-
-	/**
-	 * Build user role cache
-	 */
-	private function buildUserRoleCache(): void
-	{
-		$this->cache[self::CACHE_USER_ROLES] = JVB_USER;
-
-		foreach ($this->cache[self::CACHE_USER_ROLES] as $slug => &$config) {
-			$config['_slug'] = $slug;
-			$config['_role'] = BASE . $slug;
-			$config['_creatable_content'] = $this->extractCreatableContent($config);
-		}
-	}
 
 	/**
 	 * Build relationships between types
diff --git a/inc/utility/Validator.php b/inc/utility/Validator.php
index f77dd52..ef8201e 100644
--- a/inc/utility/Validator.php
+++ b/inc/utility/Validator.php
@@ -1,6 +1,8 @@
 <?php
 namespace JVBase\utility;
 
+use JVBase\registrar\Registrar;
+
 if (!defined('ABSPATH')) {
 	exit;
 }
@@ -528,19 +530,19 @@
 		$this->errors = [];
 		$this->warnings = [];
 
-		foreach (JVB_CONTENT ?? [] as $slug => $config) {
+		foreach (Registrar::getRegistered('post') as $slug => $config) {
 			if (isset($config['seo'])) {
 				$this->validateTypeSEOConfig($slug, $config['seo'], 'content', $config);
 			}
 		}
 
-		foreach (JVB_TAXONOMY ?? [] as $slug => $config) {
+		foreach (Registrar::getRegistered('term') as $slug => $config) {
 			if (isset($config['seo'])) {
 				$this->validateTypeSEOConfig($slug, $config['seo'], 'taxonomy', $config);
 			}
 		}
 
-		foreach (JVB_USER ?? [] as $slug => $config) {
+		foreach (Registrar::getRegistered('user') as $slug => $config) {
 			if (isset($config['seo'])) {
 				$this->validateTypeSEOConfig($slug, $config['seo'], 'user', $config);
 			}
diff --git a/jvb.php b/jvb.php
index aee9ced..21bc092 100644
--- a/jvb.php
+++ b/jvb.php
@@ -11,7 +11,8 @@
 
 use JVBase\JVB;
 use JVBase\managers\IconsManager;
-use JVBase\utility\Checker;
+use JVBase\registrar\Registrar;
+
 use JVBase\utility\Features;
 
 //security
@@ -102,32 +103,12 @@
 
 
 require(JVB_DIR.'/base/_setup.php');
-//error_log('###############################################');
-//error_log('Registered Base');
-//error_log('###############################################');
-//error_log('BASE: '.print_r(BASE, true));
-//error_log('JVB_SITE: '.print_r(JVB_SITE, true));
-//error_log('JVB_OPTIONS: '.print_r(JVB_OPTIONS, true));
-//error_log('JVB_CONTENT: '.print_r(JVB_CONTENT, true));
-//error_log('JVB_TAXONOMY: '.print_r(JVB_TAXONOMY, true));
-//error_log('JVB_LOGIN: '.print_r(JVB_LOGIN, true));
-//error_log('JVB_MEMBERSHIP: '.print_r(JVB_MEMBERSHIP, true));
-//error_log('JVB_USER: '.print_r(JVB_USER, true));
-
 
 if (empty(JVB_SITE)) {
     return;
 }
 require(JVB_DIR.'/inc/utility/setup.php');
 require(JVB_DIR.'/checks.php');
-require(JVB_DIR.'/globals.php');
-
-$jvb_feed        = jvbGlobalFeedContent();
-$jvb_taxonomy_for= jvbGlobalTaxonomyFor();
-$jvb_responses     = jvbGlobalResponses();
-
-global $jvb_everything;
-$jvb_everything = array_merge(JVB_CONTENT, JVB_TAXONOMY);
 
 
 require(JVB_DIR . '/inc/registry/_setup.php');
@@ -237,7 +218,7 @@
     }
 }
 
-require(JVB_DIR . '/inc/users/UserSettings.php');
+//require(JVB_DIR . '/inc/users/UserSettings.php');
 
 
 require(JVB_DIR . '/inc/templates.php');
@@ -260,10 +241,6 @@
 }
 
 
-function checkIf(): Checker
-{
-	return JVBase\utility\Checker::getInstance();
-}
 
 require(JVB_DIR . '/inc/blocks/_setup.php');
 
@@ -320,9 +297,7 @@
 	if (Features::forSite()->has('favourites')) {
 		$interactions[] = 'favourites';
 	}
-	if (Features::anyContentHas('karma') ||
-		Features::anyTaxonomyHas('karma') ||
-		Features::anyUserHas('karma')) {
+	if (!empty(Registrar::getFeatured('karma'))) {
 		$interactions[] = 'karma';
 	}
 	if (Features::forSite()->has('notifications')) {
@@ -340,7 +315,7 @@
 	$queue = [
 		'api' => rest_url('jvb/v1/'),
 		'redirect' => get_home_url(null, '/login/'),
-		'labels' => jvbGetLabels(),
+		'labels' => Registrar::getLabels(),
 	];
 
     wp_localize_script('jvb-auth', 'jvbSettings', $queue);
@@ -378,7 +353,7 @@
 		}';
 	}
 
-	if (Features::anyContentHas('karma') || Features::anyTaxonomyHas('karma') || Features::anyUserHas('karma')) {
+	if (!empty(Registrar::getFeatured('karma'))) {
 		wp_enqueue_script('jvb-votes');
 		$initUserSettings .= '// Fetch user votes
         try {
diff --git a/lists.php b/lists.php
deleted file mode 100644
index 0f0c218..0000000
--- a/lists.php
+++ /dev/null
@@ -1,222 +0,0 @@
-<?php
-if (!defined('ABSPATH')) {
-    exit; // Exit if accessed directly
-}
-function jvbAlphabetizeMe(
-    array $list,
-    string $name='',
-    string $url = '',
-    string $ID='',
-    $extra = false
-):array {
-    if ($name == '') {
-        $name = get_the_title();
-    }
-    if ($url == '') {
-        $url = get_the_permalink();
-    }
-    if ($ID == '') {
-        $ID = get_the_ID();
-    }
-
-    $first = strtolower(mb_substr($name, 0, 1));
-    $list[$first][] = array(
-        'name'  => $name,
-        'url'   => $url,
-        'id'    => $ID,
-        'extra' => $extra,
-    );
-    return $list;
-}
-
-function jvbGetListTypes():array
-{
-    $return = get_option(BASE.'list_types', false);
-    if (!$return) {
-        $directories = jvbGlobalDirectories();
-		$directories = array_merge($directories['content'],$directories['taxonomy']);
-        foreach ($directories as $key => $directory) {
-            $ID = jvbGetPageId($directory['page']);
-
-            $directories[$key]['ID'] = $ID;
-            $directories[$key]['url'] = get_the_permalink($ID);
-        }
-        $return = $directories;
-        $mapID = jvbGetPageId('Map');
-        $url = get_the_permalink($mapID);
-        $return[] = [
-            'slug'  => 'map',
-            'title'    => 'Map',
-            'ID'    => $mapID,
-            'url'    => $url,
-            'page'    => 'Map',
-            'type'    => 'term',
-        ];
-        update_option(BASE.'list_types', $return);
-    }
-    return $return;
-}
-
-function jvbDirectoryFrom(string|int|null $from, mixed $search):array
-{
-    $directories = jvbGlobalDirectoryInfo();
-    $col = array_column($directories, $from);
-    return $directories[array_search($search, $col)];
-}
-
-function jvbGetDirectoryIntro(string $type):string
-{
-    switch ($type) {
-        case BASE.'artist':
-            $intro = [
-                'From old-school legends to fresh talent, here\'s your backstage pass to Edmonton\'s tattoo scene.',
-                'Every artist, every style. No algorithm. No BS. Just pure Edmonton ink.'
-            ];
-            break;
-
-        case BASE.'shop':
-            $map = jvbGetPageId('Map');
-            $intro = [
-                'The beating hearts of Edmonton\'s tattoo scene. Street by street, neighborhood by neighborhood.',
-                'Your map to Edmonton\'s best tattoo shops. Real spaces. Real artists. Real ink.',
-                'Want the actual map? <a href="'.get_home_url().'/directory/map/" data-type="page" data-id="'.$map.'">Check it here</a>.'
-            ];
-            break;
-
-        case BASE.'style':
-            $intro = [
-                'From traditional roots to bleeding-edge techniques.',
-                'This is your visual dictionary to tattoo styles in Edmonton. Because great tattoos start with knowing what you want.'
-            ];
-            break;
-
-        case BASE.'theme':
-            $intro = [
-                'Stories told in ink. Symbols that speak. Imagery that inspires.',
-                'Explore what\'s possible when Edmonton artists tackle your ideas. Different styles, same subject. Pure creative freedom.'
-            ];
-            break;
-
-        case BASE.'piercing':
-            $intro = [
-                'Beyond ink: piercings, spacers, and more. The next level of body art in Edmonton.',
-                'Your guide to professional body mod artists. Because your body is a canvas, and ink is just the beginning.'
-            ];
-            break;
-
-        case BASE.'artstyle':
-            $intro = [
-                'When tattoo artists break free from skin. Their personal styles, unleashed on canvas, walls, and beyond.',
-                'Where street meets gallery. Pure Edmonton creativity, no boundaries.'
-            ];
-            break;
-
-        case BASE.'arttheme':
-            $intro = [
-                'The subjects that drive Edmonton artists. From urban landscapes to surreal visions.',
-                'See how themes transform across mediums. Same story, different canvas.'
-            ];
-            break;
-
-        case BASE.'arttype':
-            $intro = [
-                'Sculpture to canvas, digital to print. Your tattoo artist\'s other obsessions.',
-                'Discover the full range of Edmonton\'s artistic underground. Every medium, every surface.'
-            ];
-            break;
-
-        case BASE.'artmedium':
-            $intro = [
-                'Tools of the trade beyond the machine. Paint, pixels, pencils, and everything between.',
-                'Browse art by medium. Because great artists never stop experimenting.'
-            ];
-            break;
-
-        case BASE.'partner':
-            $intro = [
-                'The brands and businesses that get it. Vetted by artists, trusted by the community.',
-                'Supporting Edmonton\'s tattoo scene. No sellouts, just solid connections.'
-            ];
-            break;
-
-        case BASE.'event':
-            $intro = [
-                'Flash days, guest spots, and exclusive drops from Edmonton\'s tattoo community.',
-                'Direct from the artists to you. No middleman, no markup, just pure opportunity.'
-            ];
-            break;
-        case BASE.'offer':
-            $intro = [
-                'Exclusive deals from the brands that support Edmonton\'s tattoo scene. Supplies, gear, merch, and more.',
-                'Community vetted, artist approved. Direct from our partners to your stash.'
-            ];
-            break;
-
-        default:
-            $intro = [];
-            break;
-    }
-
-    if (empty($intro)) {
-        return '';
-    }
-
-    return implode('', array_map(function ($paragraph) {
-        return "<!-- wp:paragraph --><p>{$paragraph}</p><!-- /wp:paragraph -->";
-    }, $intro));
-}
-
-function jvbGetPageId(string $title):int
-{
-    $page = new WP_Query(
-        [
-            'post_type' => 'page',
-            'title' => $title,
-            'posts_per_page'    => 1,
-            'fields'    => 'ids',
-        ]
-    );
-    wp_reset_postdata();
-    $result = ($page->have_posts()) ? $page->posts[0] : false;
-    if (!$result) {
-        $result = wp_insert_post([
-            'post_title'    => $title,
-            'post_type'     => 'page',
-            'post_status'   => 'publish',
-        ]);
-    }
-    return $result;
-}
-
-
-function jvbLetters():array
-{
-    return array(
-        'a',
-        'b',
-        'c',
-        'd',
-        'e',
-        'f',
-        'g',
-        'h',
-        'i',
-        'j',
-        'k',
-        'l',
-        'm',
-        'n',
-        'o',
-        'p',
-        'q',
-        'r',
-        's',
-        't',
-        'u',
-        'v',
-        'w',
-        'x',
-        'y',
-        'z'
-    );
-}
diff --git a/src/feed/render.php b/src/feed/render.php
index 57c50e9..0112ad1 100644
--- a/src/feed/render.php
+++ b/src/feed/render.php
@@ -2,250 +2,3 @@
 if (!defined('ABSPATH')) {
     exit; // Exit if accessed directly
 }
-
-/**
- * Feed block render callback
- *
- * @param array    $attributes Block attributes.
- * @param string   $content    Block content.
- * @param WP_Block $block      Block instance.
- * @return string  Rendered block type output.
- */
-function jvbRenderFeedBlock($attributes, $content = '', $block = null)
-{
-
-    ob_start();
-    // Get block settings
-    global $jvb_taxonomy_for;
-    global $jvb_everything;
-    $context = '';
-
-    $taxonomies = [];
-
-    foreach ($jvb_taxonomy_for as $tax => $for) {
-        if (array_intersect($for, $attributes['contentTypes'])) {
-            $taxonomies[] = $tax;
-        }
-    }
-
-    $settings = [
-        'content'         => $attributes['contentTypes'][0] ?? 'tattoo',
-        'contentTypes'    => $attributes['contentTypes'],
-        'taxonomies'      => $taxonomies,
-        'defaultOrder'    => $attributes['defaultOrder'],
-        'itemsPerPage'    => $attributes['itemsPerPage'],
-    ];
-
-    $isGallery = false;
-    if ($attributes['inheritQuery']) {
-        $obj = get_queried_object();
-        $taxonomies = $contentTypes = [];
-        if (is_singular()) {
-            switch ($obj->post_type) {
-                case BASE.'artist':
-                case BASE.'partner':
-                    $contentTypes = jvbGetUserContentTypes(get_the_ID());
-                    break;
-            }
-
-            if ($obj->post_type == BASE.'artist') {
-                $isGallery = true;
-                $context = 'artist';
-                $name = get_post_meta($obj->id, 'jvb_first_name', true);
-                $feed_data['title'] = ($name== '') ? $obj->post_title : $name;
-            } elseif ($obj->post_type == BASE.'partner') {
-                $context = 'partner';
-                $feed_data['title'] = 'Offers:';
-            }
-            $type = 'post';
-        } elseif (is_tax('jvb_shop')) {
-            $context = 'shop';
-            $feed_data['title'] = 'At the Shop:';
-            $contentTypes = [
-                'artist',
-                'tattoo',
-                'piercing',
-//                'event'
-            ];
-            $type = 'term';
-        } elseif (is_tax()) {
-            $context = str_replace(BASE, '', $obj->taxonomy);
-
-            foreach ($jvb_taxonomy_for as $tax => $for) {
-                if ($context === $tax) {
-                    $contentTypes = $for['types'];
-                }
-            }
-            $type = 'term';
-            $context = 'taxonomy:'.$context;
-        }
-
-        if (empty($contentTypes)) {
-            return;
-        }
-        foreach ($contentTypes as $index => $type) {
-            $contentTypes[$index] = str_replace(BASE, '', $type);
-        }
-        foreach ($jvb_taxonomy_for as $tax => $for) {
-            if (array_intersect($for, $contentTypes)) {
-                $taxonomies[] = $tax;
-            }
-        }
-
-        $feed_data = [
-            'content'        => $contentTypes[0],
-            'contentTypes'     => $contentTypes,
-            'taxonomies'    => $taxonomies,
-            'context'        => $context,
-        ];
-        if ($isGallery) {
-            $feed_data['isGallery'] = true;
-        }
-
-        $settings = array_merge($settings, $feed_data);
-        $title = !array_key_exists('title', $settings) ? '' : '<h2 id="work">'.$feed_data['title'].'</h2>';
-
-    } else {
-        $title = $attributes['title'] ? "<h2>{$attributes['title']}</h2>" : '';
-    }
-
-    $many = count($settings['contentTypes']) > 1;
-    $work = is_singular(BASE.'artist') ? ' id="work"' : '';
-
-    $source = get_queried_object_id();
-    global $jvb_feed;
-
-    if (empty($settings['contentTypes'])) {
-        return;
-    }
-    ?>
-
-    <section <?= $work ?> class="feed-block"
-        data-source="<?= get_queried_object_id(); ?>"<?= ($context !== '') ? '
-        data-context="'.$context.'"' : ''    ?><?= ($isGallery) ? ' data-gallery="true"' : ''?>>
-        <?= $title ?>
-
-        <form class="feed-filters">
-            <?= (!$many) ? '' : '<details>' ?>
-            <?php if ($many) : ?>
-            <summary class="row btw">
-                <div class="type-filter label">
-                    <span>SHOW:</span>
-                </div>
-                <?php
-                foreach ($settings['contentTypes'] as $i => $type) :
-                    $checked = $i === 0 ? ' checked' : '';
-
-                    $label = $jvb_everything[$type]['plural'];
-                    ?>
-                    <div class="type-filter">
-                        <input type="radio"
-                               id="filter-<?= esc_attr($type) ?>"
-                               class="btn"
-                               name="content"
-                               value="<?= esc_attr($type) ?>"
-                            <?= $checked ?>>
-                        <label for="filter-<?= esc_attr($type) ?>" title="Show <?= $label ?>">
-                            <?= jvbIcon($type, ['title'=> $label]) ?>
-                            <span class="label"><?= $label ?></span>
-                        </label>
-                    </div>
-                <?php endforeach; ?>
-            <?php endif; ?>
-
-            <?php if (is_user_logged_in()) : ?>
-                <div class="type-filter favourites-toggle">
-                    <input type="checkbox" id="favourites_only" class="btn" name="favourites_only">
-                    <label for="favourites_only" title="Show Favourites">
-                        <?= jvbIcon('heart', ['title'    =>'Favourites']) ?>
-                        <span class="label">Favourites</span>
-                    </label>
-                </div>
-            <?php endif; ?>
-
-            <?php if ($many) {
-                echo '</summary>';
-            } ?>
-
-            <div class="filters">
-                <div class="filter-group">
-                    <div class="type-filter label">
-                        <span>FILTER:</span>
-                    </div>
-
-                    <?php foreach ($jvb_taxonomy_for as $tax => $items) :
-                        $label = $jvb_everything[$tax]['plural'];
-                        $hidden = !in_array($tax, $settings['taxonomies']) ? ' hidden' : '';
-
-                        $tax = new JVBase\Forms\TaxonomySelector(
-                            $tax,
-                            BASE.$tax,
-                            [
-                                'label' => $label,
-                                'types' => $items,
-                                'hidden'=> $hidden
-                            ]
-                        );
-                        echo  $tax->renderFeed();
-                    endforeach; ?>
-                </div>
-                    <div class="selected-items-section">
-                        <div class="selected-items"></div>
-                        <div class="filter-actions">
-                            <?= jvbRenderToggleTextField('match', 'Match', 'Filters', 'ALL', 'ANY', true) ?>
-                            <button type="button" class="clear-filters row" hidden>
-                                <?= jvbIcon('x', ['title'    => 'Clear']) ?>
-                                Clear All Filters
-                            </button>
-                        </div>
-                    </div>
-                </div>
-
-                <div class="order-options">
-                    <div class="order-by">
-                        <span class="type-filter label">ORDER:</span>
-                        <div class="radio-group-label">
-                            <input type="radio" id="order-title" class="btn" name="orderby" value="title" data-for="artist,shop" hidden>
-                            <label for="order-title" title="Order by Name">
-                                <?= jvbIcon('alphabetical') ?>
-                                <span class="label">Name</span>
-                            </label>
-
-                            <input type="radio" id="order-date" class="btn" name="orderby" value="date" checked>
-                            <label for="order-date" title="Order by Date">
-                                <?= jvbIcon('calendar', ['title'=>'Date']) ?>
-                                <span class="label">Date</span>
-                            </label>
-
-                            <input type="radio" id="order-random" class="btn" name="orderby" value="random">
-                            <label for="order-random" title="Random Order">
-                                <?= jvbIcon('shuffle') ?>
-                                <span class="label">Random</span>
-                            </label>
-                        </div>
-                    </div>
-
-                    <div class="order-direction radio-group-label" data-for-order="date,title">
-                        <input type="radio" id="order-desc" class="btn" name="order" value="desc" checked>
-                        <label for="order-desc" title="Newest First">
-                            <?= jvbIcon('sort-descending') ?>
-                        </label>
-
-                        <input type="radio" id="order-asc" class="btn" name="order" value="asc">
-                        <label for="order-asc" title="Oldest First">
-                            <?= jvbIcon('sort-ascending') ?>
-                        </label>
-                    </div>
-                </div>
-                <?= (!$many) ? '' : '</details>' ?>
-        </form>
-
-        <div class="feed-grid"></div>
-
-
-    </section>
-    <?= jvbGetFeedItemTemplate(); ?>
-    <?= jvbGetFeedArtistTemplate(); ?>
-    <?php
-    return ob_get_clean();
-}
diff --git a/src/fields/block.json b/src/fields/block.json
new file mode 100644
index 0000000..085ccdf
--- /dev/null
+++ b/src/fields/block.json
@@ -0,0 +1,25 @@
+{
+    "$schema": "https://schemas.wp.org/trunk/block.json",
+    "apiVersion": 3,
+    "name": "jvb/fields",
+    "title": "JakeVan Fields",
+    "category": "jvb",
+    "icon": "ellipses",
+    "description": "Access data from your custom fields",
+    "keywords": [ "field", "custom", "jake" ],
+    "version": "0.9.0",
+    "textdomain": "jvb",
+    "supports": {
+        "html": false,
+        "align": ["wide", "full"]
+    },
+    "selectors": {
+        "root": ".jvb-f"
+    },
+    "styles": [],
+    "render": "file:./render.php",
+    "editorScript": "file:./index.js",
+    "editorStyle": "file:./index.css",
+    "style": "file:./style-index.css",
+    "viewScript": "file:./view.js"
+}
diff --git a/src/fields/edit.js b/src/fields/edit.js
new file mode 100644
index 0000000..6741773
--- /dev/null
+++ b/src/fields/edit.js
@@ -0,0 +1,29 @@
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
+import { SelectControl, ToggleControl, PanelBody } from '@wordpress/components';
+
+/**
+ * Styles
+ */
+import './editor.scss';
+
+/**
+ * Edit function for Summary Block
+ */
+export default function Edit({ attributes, setAttributes }) {
+    const blockProps = useBlockProps();
+
+    return (
+        <div {...blockProps}>
+            <div className="jvb-summary-preview">
+                <h3>{__('Summary', 'jvb')}</h3>
+                <p className="jvb-list-preview-note">
+                    {__('This will inherit the current query to build the information from our custom meta on the front end.', 'jvb')}
+                </p>
+            </div>
+        </div>
+    );
+}
diff --git a/src/fields/editor.scss b/src/fields/editor.scss
new file mode 100644
index 0000000..bdf5776
--- /dev/null
+++ b/src/fields/editor.scss
@@ -0,0 +1,20 @@
+/**
+ * Directory List Block Editor Styles
+ */
+.jvb-summary-preview {
+    padding: 20px;
+    background-color: #f8f9fa;
+    border: 1px solid #e2e4e7;
+    border-radius: 4px;
+
+    h3 {
+        margin-top: 0;
+        padding-bottom: 10px;
+        border-bottom: 1px solid #ff0080;
+    }
+    &-note {
+        font-style: italic;
+        color: #555d66;
+        margin-bottom: 0;
+    }
+}
diff --git a/src/list/index.js b/src/fields/index.js
similarity index 100%
rename from src/list/index.js
rename to src/fields/index.js
diff --git a/src/list/index.php b/src/fields/index.php
similarity index 100%
rename from src/list/index.php
rename to src/fields/index.php
diff --git a/src/fields/render.php b/src/fields/render.php
new file mode 100644
index 0000000..05636a1
--- /dev/null
+++ b/src/fields/render.php
@@ -0,0 +1,320 @@
+<?php
+
+use JVBase\managers\Cache;
+use JVBase\meta\Meta;
+use JVBase\meta\Render;
+use JVBase\registrar\Registrar;
+
+if (!defined('ABSPATH')) {
+    exit; // Exit if accessed directly
+}
+/**
+ * Summary Block Render
+ *
+ * @package Edmonton_Ink
+ */
+
+function jvbRenderSummaryBlock(array $attributes):string
+{
+
+    // Buffer output
+    if (is_tax()) {
+        switch (get_queried_object()->taxonomy) {
+            case BASE.'shop':
+                return jvbRenderShopSummary();
+            default:
+                return jvbRenderTermSummary();
+        }
+    } elseif (is_singular()) {
+        return jvbRenderArtistSummary();
+    }
+    return '';
+}
+
+function jvbRenderArtistSummary():string
+{
+    $current = get_queried_object();
+    $cache = Cache::for('artistSummary', WEEK_IN_SECONDS);
+    $key = $current->ID;
+    $cached = $cache->get($key);
+    if ($cached !== false) {
+        return $cached;
+    }
+
+    ob_start();
+    $meta = Meta::forPost($current->ID);
+    $artist = jvbContentFromUser((int)$current->post_author);
+
+	$registrar = Registrar::getInstance($current->post_type));
+	$sections = [];
+	if ($registrar) {
+		$sections = $registrar->getSections();
+	}
+
+
+
+
+//    $handler = JVB()->getContent(str_replace(BASE,'', $current->post_type));
+    ?>
+    <nav id="artist" class="on-this-page index">
+        <label>Jump to:
+            <button type="button" aria-label="Show Index" title="Show Index" class="toggle" aria-expanded="false">
+                <?= jvbIcon('plus-square')?>
+            </button>
+        </label>
+        <ul>
+            <li><a href="#top" title="Back to Top"><?=jvbIcon('caret-circle-up')?></a></li>
+            <li><a href="#about">About</a></li>
+            <li><a href="#styles">Styles</a></li>
+            <li><a href="#contact">Contact</a></li>
+            <li><a href="#work">Work</a></li>
+        </ul>
+    </nav>
+    <header id="top">
+        <h1><small><?=(!empty($artist['city'])) ? $artist['city']['name'] :'Edmonton'?>'s Best <?= (!empty($artist['type'])) ?
+                    $artist['type']['name']:'Tattoo Artists'?>:
+            </small><?=$artist['display_name']?></h1>
+        <div>
+			<?php if (!empty($artist['shop'])) : ?>
+            <ul class="term-list shop">
+                <li>
+                    <a href="<?=$artist['shop']['url']?>" title="Learn more about <?=$artist['shop']['name']?>">
+                        <?= strtolower($artist['shop']['name'])?>
+                    </a>
+                </li>
+            </ul>
+			<?php endif; ?>
+			<?php if (!empty($artist['city'])): ?>
+            <ul class="term-list city">
+                <li>
+                    <a href="<?=$artist['city']['url']?>" title="See who else is rocking out of <?=$artist['city']['name']?>">
+                        <?= strtolower($artist['city']['name'])?>
+                    </a>
+                </li>
+            </ul>
+			<?php endif; ?>
+            <?php $styles = $meta->get('top_styles');
+            if (!empty($styles)) {
+                ?>
+                <ul class="term-list style">
+                    <?php
+                    foreach ($styles as $style) {
+                        $term = get_term((int)$style, BASE.'style');
+                        if ($term && !is_wp_error($term)) {
+                            $link = get_term_link((int)$style, BASE.'style');
+                            ?>
+                            <li>
+                                <a href="<?=$link?>" title="Learn more about <?=html_entity_decode($term->name)?>">
+                                    <?=strtolower(html_entity_decode($term->name))?>
+                                </a>
+                            </li>
+                            <?php
+                        }
+                    }
+                    ?>
+                </ul>
+                <?php
+            }
+            ?>
+        </div>
+    </header>
+    <section>
+        <details class="bio-info">
+            <summary class="row btw">
+                <h2>About <?= ($artist['name'] !== '') ? $artist['name'] : strtok($artist['display_name'], ' ')?></h2>
+            </summary>
+            <div class="columns stack-small">
+                <div class="column">
+                    <?= Render::renderFrom($meta, 'image_portrait'); ?>
+                </div>
+                <div class="column">
+					<?= Render::renderFrom($meta, 'short_bio'); ?>
+                </div>
+            </div>
+            <div id="styles">
+                <h3>Works In</h3>
+                <?= jvbGetTheTerms('style', $current->ID) ?>
+            </div>
+            <div class="contact">
+                <h3>Contact:</h3>
+                <?php
+                echo jvbRenderContactInfo($current->ID, $meta);
+                echo jvbRenderLinks($current->ID, $meta);
+                ?>
+            </div>
+
+            <div id="about">
+				<?= Render::renderFrom($meta, 'bio')?>
+            </div>
+        </details>
+    </section>
+    <section id="contact" class="">
+        <h2>Contact <?=$artist['name']?></h2>
+        <?php
+        echo jvbRenderContactInfo($current->ID, 'post');
+        echo jvbRenderLinks($current->ID, 'post');
+        ?>
+    </section>
+    <?php
+    $finished = ob_get_clean();
+    $cache->set($key, $finished);
+    return $finished;
+}
+
+function jvbRenderShopSummary()
+{
+    $current = get_queried_object();
+
+    $cache = Cache::for('shop_bio', WEEK_IN_SECONDS)->connect('taxonomy');
+    $key = $current->term_id;
+    $cached = $cache->get($key);
+    if ($cached !== false) {
+        return $cached;
+    }
+
+    ob_start();
+
+    $meta = Meta::forTerm($current->term_id);
+	$fields = $meta->getAll(['average_rating', 'established', 'bio','location','hours','specialties','awards','reviews']);
+    ?>
+    <nav id="shop" class="on-this-page index">
+        <label>Jump to:
+            <button type="button" aria-label="Show Index" title="Show Index" class="toggle" aria-expanded="false">
+                <?= jvbIcon('plus-square')?>
+            </button>
+        </label>
+        <ul>
+            <li><a href="#top" title="Back to Top"><?=jvbIcon('caret-circle-up')?></a></li> <?php
+            if ($fields['rating'] !== 'none') {
+                ?>
+                <li><a href="#rating">Rating</a></li>
+                <?php
+            } elseif ($fields['opened'] !== '') {
+                ?>
+                <li><a href="#opened">Opened</a></li>
+                <?php
+            } elseif ($fields['location'] !== '') {
+                ?>
+                <li><a href="#location">Location</a></li>
+                <?php
+            } elseif ($fields['about'] !== '') {
+                ?>
+                <li><a href="#about">About</a></li>
+                <?php
+            } elseif ($fields['hours'] !== '') {
+                ?>
+                <li><a href="#hours">Hours</a></li>
+                <?php
+            } elseif ($fields['specialties'] !== '') {
+                ?>
+                <li><a href="#specialties">Specialties</a></li>
+                <?php
+            } elseif ($fields['awards'] !== '') {
+                ?>
+                <li><a href="#awards">Awards</a></li>
+                <?php
+            } elseif ($fields['reviews'] !== '') {
+                ?>
+                <li><a href="#reviews">Reviews</a></li>
+                <?php
+            }
+            ?>
+            <li><a href="#contact">Contact</a></li>
+            <li><a href="#artists">Artists</a></li>
+        </ul>
+    </nav>
+    <header id="top">
+        <div class="columns stack-small">
+            <div class="column">
+                <?=jvbFormatImage($meta->get('image'))?>
+            </div>
+            <div class="column">
+                <h1>
+                    <small><?= (get_term((int)$meta->get('city'), BASE.'city')) ?
+                            get_term((int)$meta->get('city'), BASE.'city')->name :
+                            'Edmonton'?>'s Best Tattoo Shops</small>
+                    <?=$current->name?>
+                </h1>
+                <?= jvbFormatRating($current->term_id, 'term') ?>
+				<?= Render::renderFrom($meta,   'slogan'); ?>
+            </div>
+        </div>
+    </header>
+    <section>
+        <details class="bio-info">
+            <summary class="row btw">
+                <h2>Learn More About <?=$current->name?></h2>
+            </summary>
+            <div class="map">
+				<?= Render::renderFrom($meta, 'location'); ?>
+            </div>
+            <div class="short-bio">
+				<?= Render::renderFrom($meta, 'short_bio'); ?>
+            </div>
+
+            <div class="contact">
+                <h3>Contact:</h3>
+                <?php
+                echo jvbRenderContactInfo($current->term_id, 'term');
+                echo jvbRenderLinks($current->term_id, 'term');
+                ?>
+            </div>
+
+            <div id="about">
+				<?= Render::renderFrom($meta, 'bio')?>
+            </div>
+        </details>
+    </section>
+    <section id="contact" class="">
+        <h2>Contact </h2>
+        <?php
+        echo jvbRenderContactInfo($current->term_id, 'term');
+        echo jvbRenderLinks($current->term_id, 'term');
+        ?>
+    </section>
+    <?= jvbRenderHours($current->term_id, 'term')?>
+
+
+    <?php
+    $finished = ob_get_clean();
+    $cache->set($key, $finished);
+    return $finished;
+}
+
+
+function jvbRenderTermSummary()
+{
+    $current = get_queried_object();
+    $cache = Cache::for('term_summary', WEEK_IN_SECONDS)->connect('taxonomy');
+    $key = $current->ID;
+    $cached = $cache->get($key);
+    if ($cached !== false) {
+        return $cached;
+    }
+
+    ob_start();
+	$tax = jvbNoBase($current->taxonomy);
+    switch ($tax) {
+        case 'style':
+            $title = 'Tattoo Artists';
+            break;
+        case 'theme':
+            $title = 'Tattoos';
+            break;
+        default:
+            $title = '';
+    }
+
+    $meta = Meta::forTerm($current->ID);
+    $fields = $meta->getAll();
+
+    ?>
+    <header id="top">
+        <h1><?= get_the_archive_title() ?></h1>
+    </header>
+
+    <?php
+    $finished = ob_get_clean();
+    $cache->set($key, $finished);
+    return $finished;
+}
diff --git a/src/list/save.js b/src/fields/save.js
similarity index 100%
rename from src/list/save.js
rename to src/fields/save.js
diff --git a/src/fields/style.scss b/src/fields/style.scss
new file mode 100644
index 0000000..b182fe9
--- /dev/null
+++ b/src/fields/style.scss
@@ -0,0 +1,20 @@
+details > div {
+	margin: 1rem 0;
+}
+
+main > header:not(:has(img)) {
+	margin-top: 3rem!important;
+}
+
+header a::before {
+	display: none!important;
+}
+
+header + details {
+	margin: 1.5rem auto 3rem!important;
+	max-width: var(--wide);
+}
+
+main {
+	padding-top: 0!important;
+}
diff --git a/src/list/view.js b/src/fields/view.js
similarity index 100%
rename from src/list/view.js
rename to src/fields/view.js
diff --git a/src/list/block.json b/src/list/block.json
deleted file mode 100644
index 1af349b..0000000
--- a/src/list/block.json
+++ /dev/null
@@ -1,43 +0,0 @@
-{
-    "$schema": "https://schemas.wp.org/trunk/block.json",
-    "apiVersion": 3,
-    "name": "jvb/list",
-    "title": "Directory Lists",
-    "category": "jvb",
-    "icon": "list-view",
-    "description": "Outputs an alphabetical list of all items in the selected type.",
-    "keywords": [ "list", "directory", "alphabetical" ],
-    "version": "0.9.0",
-    "textdomain": "jvb",
-    "supports": {
-        "html": false,
-        "align": ["wide", "full"]
-    },
-    "attributes": {
-        "listType": {
-            "type": "string",
-            "default": "tattoo"
-        },
-        "refreshCache": {
-            "type": "boolean",
-            "default": false
-        }
-    },
-    "selectors": {
-        "root": ".directory-list-block"
-    },
-    "styles": [
-        { "name": "default", "label": "Default", "isDefault": true },
-        { "name": "other", "label": "Other" }
-    ],
-    "example": {
-        "attributes": {
-            "listType": "tattoo"
-        }
-    },
-    "render": "file:./render.php",
-    "editorScript": "file:./index.js",
-    "editorStyle": "file:./index.css",
-    "style": "file:./style-index.css",
-    "viewScript": "file:./view.js"
-}
diff --git a/src/list/edit.js b/src/list/edit.js
deleted file mode 100644
index b554d68..0000000
--- a/src/list/edit.js
+++ /dev/null
@@ -1,71 +0,0 @@
-/**
- * WordPress dependencies
- */
-import { __ } from '@wordpress/i18n';
-import { useBlockProps, InspectorControls } from '@wordpress/block-editor';
-import { SelectControl, ToggleControl, PanelBody } from '@wordpress/components';
-
-/**
- * Styles
- */
-import './editor.scss';
-
-/**
- * Edit function for Directory List Block
- */
-export default function Edit({ attributes, setAttributes }) {
-    const blockProps = useBlockProps();
-
-    // Access the localized list types from the global window object
-    const listTypeOptions = window.jvbListTypes ? window.jvbListTypes.map(type => {
-        return {
-            label: type.title,
-            value: type.slug
-        };
-    }) : [];
-
-    // Find the selected list type details
-    const selectedType = window.jvbListTypes ?
-        window.jvbListTypes.find(type => type.slug === attributes.listType) || {}
-        : {};
-
-    return (
-        <div {...blockProps}>
-            <InspectorControls>
-                <PanelBody title={__('List Settings', 'jvb')}>
-                    <SelectControl
-                        label={__('List Type', 'jvb')}
-                        value={attributes.listType}
-                        options={listTypeOptions}
-                        onChange={(value) => setAttributes({ listType: value })}
-                    />
-                    <ToggleControl
-                        label={__('Refresh Cache', 'jvb')}
-                        help={__('Enable to regenerate the list data on page load', 'jvb')}
-                        checked={attributes.refreshCache}
-                        onChange={(value) => setAttributes({ refreshCache: value })}
-                    />
-                </PanelBody>
-            </InspectorControls>
-
-            <div className="jvb-list-preview">
-                <h3>{__('Directory List', 'jvb')}</h3>
-                <div className="jvb-list-preview-info">
-                    <p><strong>{__('Selected List Type:', 'jvb')}</strong> {selectedType.title}</p>
-                    <p><strong>{__('Type:', 'jvb')}</strong> {selectedType.type}</p>
-                    {selectedType.links && (
-                        <p><strong>{__('Links:', 'jvb')}</strong> {selectedType.links.join(', ')}</p>
-                    )}
-                    <p><strong>{__('Cache Status:', 'jvb')}</strong>
-                        {attributes.refreshCache ?
-                            __('Will refresh on load', 'jvb') :
-                            __('Using cached data', 'jvb')}
-                    </p>
-                </div>
-                <p className="jvb-list-preview-note">
-                    {__('This alphabetical list will be rendered on the frontend.', 'jvb')}
-                </p>
-            </div>
-        </div>
-    );
-}
diff --git a/src/list/editor.scss b/src/list/editor.scss
deleted file mode 100644
index 607e1cc..0000000
--- a/src/list/editor.scss
+++ /dev/null
@@ -1,33 +0,0 @@
-/**
- * Directory List Block Editor Styles
- */
-.jvb-list-preview {
-    padding: 20px;
-    background-color: #f8f9fa;
-    border: 1px solid #e2e4e7;
-    border-radius: 4px;
-
-    h3 {
-        margin-top: 0;
-        padding-bottom: 10px;
-        border-bottom: 1px solid #e2e4e7;
-    }
-
-    &-info {
-        background-color: #fff;
-        padding: 15px;
-        border-radius: 4px;
-        margin-bottom: 15px;
-        border: 1px solid #e2e4e7;
-
-        p {
-            margin: 5px 0;
-        }
-    }
-
-    &-note {
-        font-style: italic;
-        color: #555d66;
-        margin-bottom: 0;
-    }
-}
diff --git a/src/list/render.php b/src/list/render.php
deleted file mode 100644
index 489c379..0000000
--- a/src/list/render.php
+++ /dev/null
@@ -1,175 +0,0 @@
-<?php
-if (!defined('ABSPATH')) {
-    exit; // Exit if accessed directly
-}
-/**
- * Directory List Block Render
- *
- * @package Edmonton_Ink
- */
-
-function jvbRenderListBlock(array $attributes):string
-{
-    // Get the selected list type
-    $selected_type_slug = $attributes['listType'] ?? BASE.'artist';
-    $refresh_cache = $attributes['refreshCache'] ?? false;
-    $types = jvbGlobalDirectoryInfo();
-    $umami = new JVBase\managers\UmamiTracker();
-
-    // Find the selected type in the types array
-    $selected_type = null;
-    foreach ($types as $type) {
-        if ($type['slug'] === $selected_type_slug) {
-            $selected_type = $type;
-            break;
-        }
-    }
-
-    // If no valid type was found, return an error message
-    if (!$selected_type) {
-        return '<p>Error: Selected list type not found.</p>';
-    }
-    // Buffer output
-    ob_start();
-
-    // Add the "More Lists" button
-    echo '<a class="button" href="#directory-list">'.
-         jvbIcon('plus', ['title'=>'More Lists']).' <span>More Lists</span></a>';
-
-    // Get the list data
-    $list = get_option(BASE.$selected_type['slug'].'_list');
-    $list = false;
-    // If refresh cache is enabled or list doesn't exist, regenerate it
-    if ($refresh_cache || $list === false) {
-        $list = array();
-        if ($selected_type['type'] == 'post') {
-            $get = new WP_Query(array(
-                'post_type'         => $selected_type['slug'],
-                'posts_per_page'    => -1,
-                'orderby'           => 'title',
-                'order'             => 'ASC'
-            ));
-            if ($get->have_posts()) {
-                while ($get->have_posts()) {
-                    $get->the_post();
-                    $extra = false;
-                    if (!empty($selected_type['links'])) {
-                        $extra = [];
-                        foreach ($selected_type['links'] as $item) {
-                            $terms = get_the_terms(get_the_ID(), $item);
-                            if ($terms && !is_wp_error($terms)) {
-                                $term = $terms[0];
-                                $extra[] = [
-                                    'name'  => html_entity_decode($term->name),
-                                    'url'   => get_term_link($term->term_id, $item),
-                                    'id'    => $term->term_id,
-                                    'type'  => $item,
-                                ];
-                            }
-                        }
-                    }
-                    $list = jvbAlphabetizeMe(
-                        $list,
-                        get_the_title(),
-                        get_the_permalink(),
-                        get_the_ID(),
-                        $extra
-                    );
-                }
-            }
-            wp_reset_postdata();
-        } elseif ($selected_type['type'] == 'term') {
-            $terms = get_terms(array(
-                'taxonomy'      => $selected_type['slug'],
-                'hide_empty'    => true,
-                'orderby'       => 'name',
-                'order'         => 'ASC',
-            ));
-            if ($terms) {
-                foreach ($terms as $term) {
-                    $extra = false;
-                    $list = jvbAlphabetizeMe(
-                        $list,
-						html_entity_decode($term->name),
-                        get_term_link($term->term_id, $selected_type['slug']),
-                        $term->term_id,
-                        $extra
-                    );
-                }
-            }
-        }
-        update_option(BASE.$selected_type['slug'].'_list', $list);
-    }
-
-    // Check if this is a hierarchical type
-    if (array_key_exists('hierarchical', $selected_type) && $selected_type['hierarchical']) {
-        echo jvbRenderThemesHierarchical();
-        return ob_get_clean();
-    }
-    // Build the HTML output
-    $out = '<section class="directory-list artists">';
-    if (empty($list)) {
-        $out .= '<h2>Nothing here.</h2><p>We don\'t have anything to show here yet.</p>';
-    } else {
-        $out .= '<nav class="letters on-this-page"><ul>';
-        $nav = jvbLetters();
-        $letters = array_keys($list);
-        foreach ($nav as $l) {
-            $aOpen = $aClose = $class = '';
-            if (in_array($l, $letters)) {
-                $aOpen = '<a href="#starts-with-'.$l.'">';
-                $aClose = '</a>';
-                $class = ' class="has"';
-            }
-            $out .= '<li'.$class.'>'.$aOpen.strtoupper($l).$aClose.'</li>';
-        }
-        $out .= '</ul></nav><ul class="list-none">';
-        foreach ($list as $letter => $artists) {
-            $out .= '<li id="starts-with-'.$letter.'"><h3>'.strtoupper($letter).'</h3><ul>';
-            foreach ($artists as $a) {
-                $extra = '';
-                if ($a['extra'] !== false) {
-                    $extra = '<span>';
-                    foreach ($a['extra'] as $ext) {
-                        $umamiType = 'click_taxonomy';
-                        if ($ext['type'] == BASE.'shop') {
-                            $umamiType = 'click_shop';
-                        }
-                        $extra .= '<a href="'.$ext['url'].'" '.
-                                  $umami->attributesToString(
-                                      $umami->buildAttributes(
-                                          $umamiType,
-                                          $selected_type['slug'],
-                                          ['source_type' => 'directory']
-                                      )
-                                  ).'>'.$ext['name'].'</a>';
-                    }
-                    $extra .= '</span>';
-                }
-                $umamiType = 'click_profile';
-                if ($selected_type['slug'] == BASE.'shop') {
-                    $umamiType = 'click_shop';
-                } elseif ($selected_type['type'] == 'taxonomy') {
-                    $umamiType = 'click_taxonomy';
-                }
-
-                $out .= '<li><a href="'.$a['url'].'" title="More about '.$a['name'].'" '.
-                        $umami->attributesToString(
-                            $umami->buildAttributes(
-                                $umamiType,
-                                $selected_type['slug'],
-                                ['source_type' => 'directory']
-                            )
-                        ).'>'.$a['name'].'</a>'.$extra.'</li>';
-            }
-            $out .= '</ul></li>';
-        }
-        $out .= '</ul>';
-    }
-    $out .= '</section>';
-
-    echo $out;
-    echo JVB()->directories()?->renderNavigation();
-
-    return ob_get_clean();
-}
diff --git a/src/list/style.scss b/src/list/style.scss
deleted file mode 100644
index f7db6e2..0000000
--- a/src/list/style.scss
+++ /dev/null
@@ -1,46 +0,0 @@
-.directory-list {
-    ul {
-        list-style: none;
-        margin: 0;
-        padding: 0;
-    }
-    > ul li {
-        position: relative;
-        h3 {
-            position: sticky;
-            left: 0;
-            width: 100%;
-            text-align: center;
-            margin: 0!important;
-            padding: .5rem 1rem;
-            font-size: 20vw;
-            background-color: var(--base-50);
-            z-index: 5;
-            top: 0;
-        }
-        li {
-            padding: .5rem 1rem;
-            display: flex;
-            justify-content: space-between;
-            border-radius: var(--radius);
-        }
-        > ul {
-            display: flex;
-            flex-direction: column;
-            gap: .125rem;
-        }
-        li:nth-of-type(odd){
-            background-color: var(--base-200);
-        }
-        li:nth-of-type(even){
-            background-color: var(--base-100);
-        }
-    }
-}
-
-
-@media (min-width: 768px){
-    .directory-list > ul li h3 {
-        font-size: 10vw;
-    }
-}
diff --git a/src/summary/render.php b/src/summary/render.php
index f0e00c7..05636a1 100644
--- a/src/summary/render.php
+++ b/src/summary/render.php
@@ -3,6 +3,7 @@
 use JVBase\managers\Cache;
 use JVBase\meta\Meta;
 use JVBase\meta\Render;
+use JVBase\registrar\Registrar;
 
 if (!defined('ABSPATH')) {
     exit; // Exit if accessed directly
@@ -44,7 +45,12 @@
     $meta = Meta::forPost($current->ID);
     $artist = jvbContentFromUser((int)$current->post_author);
 
-	$sections = JVB_CONTENT[jvbNoBase($current->post_type)]['sections']??[];
+	$registrar = Registrar::getInstance($current->post_type));
+	$sections = [];
+	if ($registrar) {
+		$sections = $registrar->getSections();
+	}
+
 
 
 
@@ -300,7 +306,7 @@
     }
 
     $meta = Meta::forTerm($current->ID);
-    $fields = JVB_TAXONOMY[$tax]['fields']??[];
+    $fields = $meta->getAll();
 
     ?>
     <header id="top">

--
Gitblit v1.10.0